wininet: Reimplement IsUrlCacheEntryExpired.
[wine] / dlls / wininet / tests / urlcache.c
1 /*
2  * URL Cache Tests
3  *
4  * Copyright 2008 Robert Shearman for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "wininet.h"
28 #include "winineti.h"
29
30 #include "wine/test.h"
31
32 #define TEST_URL    "http://urlcachetest.winehq.org/index.html"
33 #define TEST_URL1   "Visited: user@http://urlcachetest.winehq.org/index.html"
34
35 static BOOL (WINAPI *pDeleteUrlCacheEntryA)(LPCSTR);
36 static BOOL (WINAPI *pUnlockUrlCacheEntryFileA)(LPCSTR,DWORD);
37
38 static char filenameA[MAX_PATH + 1];
39 static char filenameA1[MAX_PATH + 1];
40
41 static void check_cache_entry_infoA(const char *returnedfrom, LPINTERNET_CACHE_ENTRY_INFO lpCacheEntryInfo)
42 {
43     ok(lpCacheEntryInfo->dwStructSize == sizeof(*lpCacheEntryInfo), "%s: dwStructSize was %d\n", returnedfrom, lpCacheEntryInfo->dwStructSize);
44     ok(!strcmp(lpCacheEntryInfo->lpszSourceUrlName, TEST_URL), "%s: lpszSourceUrlName should be %s instead of %s\n", returnedfrom, TEST_URL, lpCacheEntryInfo->lpszSourceUrlName);
45     ok(!strcmp(lpCacheEntryInfo->lpszLocalFileName, filenameA), "%s: lpszLocalFileName should be %s instead of %s\n", returnedfrom, filenameA, lpCacheEntryInfo->lpszLocalFileName);
46     ok(!strcmp(lpCacheEntryInfo->lpszFileExtension, "html"), "%s: lpszFileExtension should be html instead of %s\n", returnedfrom, lpCacheEntryInfo->lpszFileExtension);
47 }
48
49 static void test_find_url_cache_entriesA(void)
50 {
51     BOOL ret;
52     HANDLE hEnumHandle;
53     BOOL found = FALSE;
54     DWORD cbCacheEntryInfo;
55     DWORD cbCacheEntryInfoSaved;
56     LPINTERNET_CACHE_ENTRY_INFO lpCacheEntryInfo;
57
58     cbCacheEntryInfo = 0;
59     SetLastError(0xdeadbeef);
60     hEnumHandle = FindFirstUrlCacheEntry(NULL, NULL, &cbCacheEntryInfo);
61     ok(!hEnumHandle, "FindFirstUrlCacheEntry should have failed\n");
62     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER, "FindFirstUrlCacheEntry should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
63     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo * sizeof(char));
64     cbCacheEntryInfoSaved = cbCacheEntryInfo;
65     hEnumHandle = FindFirstUrlCacheEntry(NULL, lpCacheEntryInfo, &cbCacheEntryInfo);
66     ok(hEnumHandle != NULL, "FindFirstUrlCacheEntry failed with error %d\n", GetLastError());
67     while (TRUE)
68     {
69         if (!strcmp(lpCacheEntryInfo->lpszSourceUrlName, TEST_URL))
70         {
71             found = TRUE;
72             break;
73         }
74         SetLastError(0xdeadbeef);
75         cbCacheEntryInfo = cbCacheEntryInfoSaved;
76         ret = FindNextUrlCacheEntry(hEnumHandle, lpCacheEntryInfo, &cbCacheEntryInfo);
77         if (!ret)
78         {
79             if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
80             {
81                 lpCacheEntryInfo = HeapReAlloc(GetProcessHeap(), 0, lpCacheEntryInfo, cbCacheEntryInfo);
82                 cbCacheEntryInfoSaved = cbCacheEntryInfo;
83                 ret = FindNextUrlCacheEntry(hEnumHandle, lpCacheEntryInfo, &cbCacheEntryInfo);
84             }
85         }
86         ok(ret, "FindNextUrlCacheEntry failed with error %d\n", GetLastError());
87         if (!ret)
88             break;
89     }
90     ok(found, "committed url cache entry not found during enumeration\n");
91
92     ret = FindCloseUrlCache(hEnumHandle);
93     ok(ret, "FindCloseUrlCache failed with error %d\n", GetLastError());
94 }
95
96 static void test_GetUrlCacheEntryInfoExA(void)
97 {
98     BOOL ret;
99     DWORD cbCacheEntryInfo, cbRedirectUrl;
100     LPINTERNET_CACHE_ENTRY_INFO lpCacheEntryInfo;
101
102     SetLastError(0xdeadbeef);
103     ret = GetUrlCacheEntryInfoEx(NULL, NULL, NULL, NULL, NULL, NULL, 0);
104     ok(!ret, "GetUrlCacheEntryInfoEx with NULL URL and NULL args should have failed\n");
105     ok(GetLastError() == ERROR_INVALID_PARAMETER,
106        "GetUrlCacheEntryInfoEx with NULL URL and NULL args should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
107
108     cbCacheEntryInfo = sizeof(INTERNET_CACHE_ENTRY_INFO);
109     SetLastError(0xdeadbeef);
110     ret = GetUrlCacheEntryInfoEx("", NULL, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
111     ok(!ret, "GetUrlCacheEntryInfoEx with zero-length buffer should fail\n");
112     ok(GetLastError() == ERROR_FILE_NOT_FOUND,
113        "GetUrlCacheEntryInfoEx should have set last error to ERROR_FILE_NOT_FOUND instead of %d\n", GetLastError());
114
115     ret = GetUrlCacheEntryInfoEx(TEST_URL, NULL, NULL, NULL, NULL, NULL, 0);
116     ok(ret, "GetUrlCacheEntryInfoEx with NULL args failed with error %d\n", GetLastError());
117
118     cbCacheEntryInfo = 0;
119     SetLastError(0xdeadbeef);
120     ret = GetUrlCacheEntryInfoEx(TEST_URL, NULL, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
121     ok(!ret, "GetUrlCacheEntryInfoEx with zero-length buffer should fail\n");
122     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
123        "GetUrlCacheEntryInfoEx should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
124
125     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
126
127     SetLastError(0xdeadbeef);
128     ret = GetUrlCacheEntryInfoEx(TEST_URL, lpCacheEntryInfo, &cbCacheEntryInfo, NULL, NULL, NULL, 0x200);
129     ok(!ret, "GetUrlCacheEntryInfoEx succeeded\n");
130     ok(GetLastError() == ERROR_FILE_NOT_FOUND,
131        "GetUrlCacheEntryInfoEx should have set last error to ERROR_FILE_NOT_FOUND instead of %d\n", GetLastError());
132
133     ret = GetUrlCacheEntryInfoEx(TEST_URL, lpCacheEntryInfo, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
134     ok(ret, "GetUrlCacheEntryInfoEx failed with error %d\n", GetLastError());
135
136     check_cache_entry_infoA("GetUrlCacheEntryInfoEx", lpCacheEntryInfo);
137
138     cbCacheEntryInfo = 100000;
139     SetLastError(0xdeadbeef);
140     ret = GetUrlCacheEntryInfoEx(TEST_URL, NULL, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
141     ok(!ret, "GetUrlCacheEntryInfoEx with zero-length buffer should fail\n");
142     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER, "GetUrlCacheEntryInfoEx should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
143
144     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
145
146     /* Querying the redirect URL fails with ERROR_INVALID_PARAMETER */
147     SetLastError(0xdeadbeef);
148     ret = GetUrlCacheEntryInfoEx(TEST_URL, NULL, NULL, NULL, &cbRedirectUrl, NULL, 0);
149     ok(GetLastError() == ERROR_INVALID_PARAMETER,
150        "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
151     SetLastError(0xdeadbeef);
152     ret = GetUrlCacheEntryInfoEx(TEST_URL, NULL, &cbCacheEntryInfo, NULL, &cbRedirectUrl, NULL, 0);
153     ok(GetLastError() == ERROR_INVALID_PARAMETER,
154        "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
155 }
156
157 static void test_RetrieveUrlCacheEntryA(void)
158 {
159     BOOL ret;
160     DWORD cbCacheEntryInfo;
161
162     cbCacheEntryInfo = 0;
163     SetLastError(0xdeadbeef);
164     ret = RetrieveUrlCacheEntryFile(NULL, NULL, &cbCacheEntryInfo, 0);
165     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
166     ok(GetLastError() == ERROR_INVALID_PARAMETER, "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
167
168     if (0)
169     {
170         /* Crashes on Win9x, NT4 and W2K */
171         SetLastError(0xdeadbeef);
172         ret = RetrieveUrlCacheEntryFile(TEST_URL, NULL, NULL, 0);
173         ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
174         ok(GetLastError() == ERROR_INVALID_PARAMETER, "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
175     }
176
177     SetLastError(0xdeadbeef);
178     cbCacheEntryInfo = 100000;
179     ret = RetrieveUrlCacheEntryFile(NULL, NULL, &cbCacheEntryInfo, 0);
180     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
181     ok(GetLastError() == ERROR_INVALID_PARAMETER, "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
182 }
183
184 static void test_IsUrlCacheEntryExpiredA(void)
185 {
186     static const char uncached_url[] =
187         "What's the airspeed velocity of an unladen swallow?";
188     BOOL ret;
189     FILETIME ft;
190     DWORD size;
191     LPINTERNET_CACHE_ENTRY_INFO info;
192     ULARGE_INTEGER exp_time;
193
194     /* The function returns TRUE when the output time is NULL or the tested URL
195      * is NULL.
196      */
197     ret = IsUrlCacheEntryExpiredA(NULL, 0, NULL);
198     ok(ret, "expected TRUE\n");
199     ft.dwLowDateTime = 0xdeadbeef;
200     ft.dwHighDateTime = 0xbaadf00d;
201     ret = IsUrlCacheEntryExpiredA(NULL, 0, &ft);
202     ok(ret, "expected TRUE\n");
203     ok(ft.dwLowDateTime == 0xdeadbeef && ft.dwHighDateTime == 0xbaadf00d,
204        "expected time to be unchanged, got (%u,%u)\n",
205        ft.dwLowDateTime, ft.dwHighDateTime);
206     ret = IsUrlCacheEntryExpiredA(TEST_URL, 0, NULL);
207     ok(ret, "expected TRUE\n");
208
209     /* The return value should indicate whether the URL is expired,
210      * and the filetime indicates the last modified time, but a cache entry
211      * with a zero expire time is "not expired".
212      */
213     ft.dwLowDateTime = 0xdeadbeef;
214     ft.dwHighDateTime = 0xbaadf00d;
215     ret = IsUrlCacheEntryExpiredA(TEST_URL, 0, &ft);
216     ok(!ret, "expected FALSE\n");
217     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
218        "expected time (0,0), got (%u,%u)\n",
219        ft.dwLowDateTime, ft.dwHighDateTime);
220
221     /* Same behavior with bogus flags. */
222     ft.dwLowDateTime = 0xdeadbeef;
223     ft.dwHighDateTime = 0xbaadf00d;
224     ret = IsUrlCacheEntryExpiredA(TEST_URL, 0xffffffff, &ft);
225     ok(!ret, "expected FALSE\n");
226     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
227        "expected time (0,0), got (%u,%u)\n",
228        ft.dwLowDateTime, ft.dwHighDateTime);
229
230     /* Set the expire time to a point in the past.. */
231     ret = GetUrlCacheEntryInfo(TEST_URL, NULL, &size);
232     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
233     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
234        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
235     info = HeapAlloc(GetProcessHeap(), 0, size);
236     ret = GetUrlCacheEntryInfo(TEST_URL, info, &size);
237     GetSystemTimeAsFileTime(&info->ExpireTime);
238     exp_time.LowPart = info->ExpireTime.dwLowDateTime;
239     exp_time.HighPart = info->ExpireTime.dwHighDateTime;
240     exp_time.QuadPart -= 10 * 60 * (ULONGLONG)10000000;
241     info->ExpireTime.dwLowDateTime = exp_time.LowPart;
242     info->ExpireTime.dwHighDateTime = exp_time.HighPart;
243     ret = SetUrlCacheEntryInfo(TEST_URL, info, CACHE_ENTRY_EXPTIME_FC);
244     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
245     ft.dwLowDateTime = 0xdeadbeef;
246     ft.dwHighDateTime = 0xbaadf00d;
247     /* and the entry should be expired. */
248     ret = IsUrlCacheEntryExpiredA(TEST_URL, 0, &ft);
249     ok(ret, "expected TRUE\n");
250     /* The modified time returned is 0. */
251     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
252        "expected time (0,0), got (%u,%u)\n",
253        ft.dwLowDateTime, ft.dwHighDateTime);
254     /* Set the expire time to a point in the future.. */
255     exp_time.QuadPart += 20 * 60 * (ULONGLONG)10000000;
256     info->ExpireTime.dwLowDateTime = exp_time.LowPart;
257     info->ExpireTime.dwHighDateTime = exp_time.HighPart;
258     ret = SetUrlCacheEntryInfo(TEST_URL, info, CACHE_ENTRY_EXPTIME_FC);
259     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
260     ft.dwLowDateTime = 0xdeadbeef;
261     ft.dwHighDateTime = 0xbaadf00d;
262     /* and the entry should no longer be expired. */
263     ret = IsUrlCacheEntryExpiredA(TEST_URL, 0, &ft);
264     ok(!ret, "expected FALSE\n");
265     /* The modified time returned is still 0. */
266     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
267        "expected time (0,0), got (%u,%u)\n",
268        ft.dwLowDateTime, ft.dwHighDateTime);
269     /* Set the modified time... */
270     GetSystemTimeAsFileTime(&info->LastModifiedTime);
271     ret = SetUrlCacheEntryInfo(TEST_URL, info, CACHE_ENTRY_MODTIME_FC);
272     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
273     /* and the entry should still be unexpired.. */
274     ret = IsUrlCacheEntryExpiredA(TEST_URL, 0, &ft);
275     ok(!ret, "expected FALSE\n");
276     /* but the modified time returned is the last modified time just set. */
277     ok(ft.dwLowDateTime == info->LastModifiedTime.dwLowDateTime &&
278        ft.dwHighDateTime == info->LastModifiedTime.dwHighDateTime,
279        "expected time (%u,%u), got (%u,%u)\n",
280        info->LastModifiedTime.dwLowDateTime,
281        info->LastModifiedTime.dwHighDateTime,
282        ft.dwLowDateTime, ft.dwHighDateTime);
283     HeapFree(GetProcessHeap(), 0, info);
284
285     /* An uncached URL is implicitly expired, but with unknown time. */
286     ft.dwLowDateTime = 0xdeadbeef;
287     ft.dwHighDateTime = 0xbaadf00d;
288     ret = IsUrlCacheEntryExpiredA(uncached_url, 0, &ft);
289     ok(ret, "expected TRUE\n");
290     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
291        "expected time (0,0), got (%u,%u)\n",
292        ft.dwLowDateTime, ft.dwHighDateTime);
293 }
294
295 static void _check_file_exists(LONG l, LPCSTR filename)
296 {
297     HANDLE file;
298
299     file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
300                        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
301     ok_(__FILE__,l)(file != INVALID_HANDLE_VALUE,
302                     "expected file to exist, CreateFile failed with error %d\n",
303                     GetLastError());
304     CloseHandle(file);
305 }
306
307 #define check_file_exists(f) _check_file_exists(__LINE__, f)
308
309 static void _check_file_not_exists(LONG l, LPCSTR filename)
310 {
311     HANDLE file;
312
313     file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
314                        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
315     ok_(__FILE__,l)(file == INVALID_HANDLE_VALUE,
316                     "expected file not to exist\n");
317     if (file != INVALID_HANDLE_VALUE)
318         CloseHandle(file);
319 }
320
321 #define check_file_not_exists(f) _check_file_not_exists(__LINE__, f)
322
323 static void create_and_write_file(LPCSTR filename, void *data, DWORD len)
324 {
325     HANDLE file;
326     DWORD written;
327     BOOL ret;
328
329     file = CreateFileA(filename, GENERIC_WRITE,
330                        FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
331                        FILE_ATTRIBUTE_NORMAL, NULL);
332     ok(file != INVALID_HANDLE_VALUE, "CreateFileA failed with error %d\n", GetLastError());
333
334     ret = WriteFile(file, data, len, &written, NULL);
335     ok(ret, "WriteFile failed with error %d\n", GetLastError());
336
337     CloseHandle(file);
338 }
339
340 static void test_urlcacheA(void)
341 {
342     static char ok_header[] = "HTTP/1.0 200 OK\r\n\r\n";
343     BOOL ret;
344     HANDLE hFile;
345     BYTE zero_byte = 0;
346     LPINTERNET_CACHE_ENTRY_INFO lpCacheEntryInfo;
347     DWORD cbCacheEntryInfo;
348     static const FILETIME filetime_zero;
349     FILETIME now;
350
351     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA, 0);
352     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
353
354     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA1, 0);
355     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
356
357     ok(lstrcmpiA(filenameA, filenameA1), "expected a different file name\n");
358
359     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
360
361     ret = CommitUrlCacheEntry(TEST_URL1, NULL, filetime_zero, filetime_zero, NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY, NULL, 0, NULL, NULL);
362     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
363     cbCacheEntryInfo = 0;
364     ret = GetUrlCacheEntryInfo(TEST_URL1, NULL, &cbCacheEntryInfo);
365     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
366     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
367        "GetUrlCacheEntryInfo should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
368     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
369     ret = GetUrlCacheEntryInfo(TEST_URL1, lpCacheEntryInfo, &cbCacheEntryInfo);
370     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
371     ok(!memcmp(&lpCacheEntryInfo->ExpireTime, &filetime_zero, sizeof(FILETIME)),
372        "expected zero ExpireTime\n");
373     ok(!memcmp(&lpCacheEntryInfo->LastModifiedTime, &filetime_zero, sizeof(FILETIME)),
374        "expected zero LastModifiedTime\n");
375     ok(lpCacheEntryInfo->CacheEntryType == (NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY) ||
376        broken(lpCacheEntryInfo->CacheEntryType == NORMAL_CACHE_ENTRY /* NT4/W2k */),
377        "expected type NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY, got %08x\n",
378        lpCacheEntryInfo->CacheEntryType);
379     ok(!lpCacheEntryInfo->dwExemptDelta, "expected dwExemptDelta 0, got %d\n",
380        lpCacheEntryInfo->dwExemptDelta);
381     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
382
383     /* A subsequent commit with a different time/type doesn't change the type */
384     GetSystemTimeAsFileTime(&now);
385     ret = CommitUrlCacheEntry(TEST_URL1, NULL, now, now, NORMAL_CACHE_ENTRY,
386             (LPBYTE)ok_header, strlen(ok_header), NULL, NULL);
387     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
388     cbCacheEntryInfo = 0;
389     ret = GetUrlCacheEntryInfo(TEST_URL1, NULL, &cbCacheEntryInfo);
390     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
391     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
392        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
393     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
394     ret = GetUrlCacheEntryInfo(TEST_URL1, lpCacheEntryInfo, &cbCacheEntryInfo);
395     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
396     /* but it does change the time.. */
397     todo_wine
398     ok(memcmp(&lpCacheEntryInfo->ExpireTime, &filetime_zero, sizeof(FILETIME)),
399        "expected positive ExpireTime\n");
400     todo_wine
401     ok(memcmp(&lpCacheEntryInfo->LastModifiedTime, &filetime_zero, sizeof(FILETIME)),
402        "expected positive LastModifiedTime\n");
403     ok(lpCacheEntryInfo->CacheEntryType == (NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY) ||
404        broken(lpCacheEntryInfo->CacheEntryType == NORMAL_CACHE_ENTRY /* NT4/W2k */),
405        "expected type NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY, got %08x\n",
406        lpCacheEntryInfo->CacheEntryType);
407     /* and set the headers. */
408     todo_wine
409     ok(lpCacheEntryInfo->dwHeaderInfoSize == 19,
410        "expected headers size 19, got %d\n",
411        lpCacheEntryInfo->dwHeaderInfoSize);
412     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
413
414     ret = CommitUrlCacheEntry(TEST_URL, filenameA, filetime_zero, filetime_zero, NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
415     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
416
417     cbCacheEntryInfo = 0;
418     SetLastError(0xdeadbeef);
419     ret = RetrieveUrlCacheEntryFile(TEST_URL, NULL, &cbCacheEntryInfo, 0);
420     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
421     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
422        "RetrieveUrlCacheEntryFile should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
423
424     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
425     ret = RetrieveUrlCacheEntryFile(TEST_URL, lpCacheEntryInfo, &cbCacheEntryInfo, 0);
426     ok(ret, "RetrieveUrlCacheEntryFile failed with error %d\n", GetLastError());
427
428     check_cache_entry_infoA("RetrieveUrlCacheEntryFile", lpCacheEntryInfo);
429
430     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
431
432     cbCacheEntryInfo = 0;
433     SetLastError(0xdeadbeef);
434     ret = RetrieveUrlCacheEntryFile(TEST_URL1, NULL, &cbCacheEntryInfo, 0);
435     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
436     ok(GetLastError() == ERROR_INVALID_DATA,
437        "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_DATA instead of %d\n", GetLastError());
438
439     if (pUnlockUrlCacheEntryFileA)
440     {
441         ret = pUnlockUrlCacheEntryFileA(TEST_URL, 0);
442         ok(ret, "UnlockUrlCacheEntryFileA failed with error %d\n", GetLastError());
443     }
444
445     /* test Find*UrlCacheEntry functions */
446     test_find_url_cache_entriesA();
447
448     test_GetUrlCacheEntryInfoExA();
449     test_RetrieveUrlCacheEntryA();
450     test_IsUrlCacheEntryExpiredA();
451
452     if (pDeleteUrlCacheEntryA)
453     {
454         ret = pDeleteUrlCacheEntryA(TEST_URL);
455         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
456         ret = pDeleteUrlCacheEntryA(TEST_URL1);
457         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
458     }
459
460     SetLastError(0xdeadbeef);
461     ret = DeleteFile(filenameA);
462     todo_wine
463     ok(!ret && GetLastError() == ERROR_FILE_NOT_FOUND, "local file should no longer exist\n");
464
465     /* Creating two entries with the same URL */
466     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA, 0);
467     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
468
469     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA1, 0);
470     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
471
472     ok(lstrcmpiA(filenameA, filenameA1), "expected a different file name\n");
473
474     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
475     create_and_write_file(filenameA1, &zero_byte, sizeof(zero_byte));
476     check_file_exists(filenameA);
477     check_file_exists(filenameA1);
478
479     ret = CommitUrlCacheEntry(TEST_URL, filenameA, filetime_zero,
480             filetime_zero, NORMAL_CACHE_ENTRY, (LPBYTE)ok_header,
481             strlen(ok_header), "html", NULL);
482     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
483     check_file_exists(filenameA);
484     check_file_exists(filenameA1);
485     ret = CommitUrlCacheEntry(TEST_URL, filenameA1, filetime_zero,
486             filetime_zero, COOKIE_CACHE_ENTRY, NULL, 0, "html", NULL);
487     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
488     /* By committing the same URL a second time, the prior entry is
489      * overwritten...
490      */
491     cbCacheEntryInfo = 0;
492     SetLastError(0xdeadbeef);
493     ret = GetUrlCacheEntryInfo(TEST_URL, NULL, &cbCacheEntryInfo);
494     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
495     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
496        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
497     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
498     ret = GetUrlCacheEntryInfo(TEST_URL, lpCacheEntryInfo, &cbCacheEntryInfo);
499     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
500     /* with the previous entry type retained.. */
501     ok(lpCacheEntryInfo->CacheEntryType & NORMAL_CACHE_ENTRY,
502        "expected cache entry type NORMAL_CACHE_ENTRY, got %d (0x%08x)\n",
503        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
504     /* and the headers overwritten.. */
505     todo_wine
506     ok(!lpCacheEntryInfo->dwHeaderInfoSize, "expected headers size 0, got %d\n",
507        lpCacheEntryInfo->dwHeaderInfoSize);
508     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
509     /* and the previous filename shouldn't exist. */
510     todo_wine
511     check_file_not_exists(filenameA);
512     check_file_exists(filenameA1);
513
514     if (pDeleteUrlCacheEntryA)
515     {
516         ret = pDeleteUrlCacheEntryA(TEST_URL);
517         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
518         todo_wine
519         check_file_not_exists(filenameA);
520         todo_wine
521         check_file_not_exists(filenameA1);
522         /* Just in case, clean up files */
523         DeleteFileA(filenameA1);
524         DeleteFileA(filenameA);
525     }
526
527     /* Check whether a retrieved cache entry can be deleted before it's
528      * unlocked:
529      */
530     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA, 0);
531     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
532     ret = CommitUrlCacheEntry(TEST_URL, filenameA, filetime_zero, filetime_zero,
533             NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
534     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
535
536     cbCacheEntryInfo = 0;
537     SetLastError(0xdeadbeef);
538     ret = RetrieveUrlCacheEntryFile(TEST_URL, NULL, &cbCacheEntryInfo, 0);
539     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
540     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
541        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
542
543     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
544     ret = RetrieveUrlCacheEntryFile(TEST_URL, lpCacheEntryInfo,
545             &cbCacheEntryInfo, 0);
546     ok(ret, "RetrieveUrlCacheEntryFile failed with error %d\n", GetLastError());
547
548     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
549
550     if (pDeleteUrlCacheEntryA)
551     {
552         ret = pDeleteUrlCacheEntryA(TEST_URL);
553         todo_wine
554         ok(!ret, "Expected failure\n");
555         todo_wine
556         ok(GetLastError() == ERROR_SHARING_VIOLATION,
557            "Expected ERROR_SHARING_VIOLATION, got %d\n", GetLastError());
558         check_file_exists(filenameA);
559     }
560     if (pUnlockUrlCacheEntryFileA)
561     {
562         check_file_exists(filenameA);
563         ret = pUnlockUrlCacheEntryFileA(TEST_URL, 0);
564         todo_wine
565         ok(ret, "UnlockUrlCacheEntryFileA failed: %d\n", GetLastError());
566         /* By unlocking the already-deleted cache entry, the file associated
567          * with it is deleted..
568          */
569         todo_wine
570         check_file_not_exists(filenameA);
571         /* (just in case, delete file) */
572         DeleteFileA(filenameA);
573     }
574     if (pDeleteUrlCacheEntryA)
575     {
576         /* and a subsequent deletion should fail. */
577         ret = pDeleteUrlCacheEntryA(TEST_URL);
578         ok(!ret, "Expected failure\n");
579         ok(GetLastError() == ERROR_FILE_NOT_FOUND,
580            "expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());
581     }
582
583     /* Test whether preventing a file from being deleted causes
584      * DeleteUrlCacheEntryA to fail.
585      */
586     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA, 0);
587     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
588
589     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
590     check_file_exists(filenameA);
591
592     ret = CommitUrlCacheEntry(TEST_URL, filenameA, filetime_zero,
593             filetime_zero, NORMAL_CACHE_ENTRY, (LPBYTE)ok_header,
594             strlen(ok_header), "html", NULL);
595     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
596     check_file_exists(filenameA);
597     hFile = CreateFileA(filenameA, GENERIC_READ, 0, NULL, OPEN_EXISTING,
598             FILE_ATTRIBUTE_NORMAL, NULL);
599     ok(hFile != INVALID_HANDLE_VALUE, "CreateFileA failed: %d\n",
600        GetLastError());
601     if (pDeleteUrlCacheEntryA)
602     {
603         /* DeleteUrlCacheEntryA should succeed.. */
604         ret = pDeleteUrlCacheEntryA(TEST_URL);
605         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
606     }
607     CloseHandle(hFile);
608     if (pDeleteUrlCacheEntryA)
609     {
610         /* and a subsequent deletion should fail.. */
611         ret = pDeleteUrlCacheEntryA(TEST_URL);
612         ok(!ret, "Expected failure\n");
613         ok(GetLastError() == ERROR_FILE_NOT_FOUND,
614            "expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());
615     }
616     /* and the file should be untouched. */
617     check_file_exists(filenameA);
618     DeleteFileA(filenameA);
619
620     /* Try creating a sticky entry.  Unlike non-sticky entries, the filename
621      * must have been set already.
622      */
623     SetLastError(0xdeadbeef);
624     ret = CommitUrlCacheEntry(TEST_URL, NULL, filetime_zero, filetime_zero,
625             STICKY_CACHE_ENTRY, (LPBYTE)ok_header, strlen(ok_header), "html",
626             NULL);
627     ok(!ret, "expected failure\n");
628     ok(GetLastError() == ERROR_INVALID_PARAMETER,
629        "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
630     SetLastError(0xdeadbeef);
631     ret = CommitUrlCacheEntry(TEST_URL, NULL, filetime_zero, filetime_zero,
632             NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY,
633             (LPBYTE)ok_header, strlen(ok_header), "html", NULL);
634     ok(!ret, "expected failure\n");
635     ok(GetLastError() == ERROR_INVALID_PARAMETER,
636        "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
637     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA, 0);
638     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
639     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
640     ret = CommitUrlCacheEntry(TEST_URL, filenameA, filetime_zero, filetime_zero,
641             NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY,
642             (LPBYTE)ok_header, strlen(ok_header), "html", NULL);
643     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
644     cbCacheEntryInfo = 0;
645     SetLastError(0xdeadbeef);
646     ret = GetUrlCacheEntryInfo(TEST_URL, NULL, &cbCacheEntryInfo);
647     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
648     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
649        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
650     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
651     ret = GetUrlCacheEntryInfo(TEST_URL, lpCacheEntryInfo, &cbCacheEntryInfo);
652     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
653     ok(lpCacheEntryInfo->CacheEntryType & (NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY),
654        "expected cache entry type NORMAL_CACHE_ENTRY | STICKY_CACHE_ENTRY, got %d (0x%08x)\n",
655        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
656     ok(lpCacheEntryInfo->dwExemptDelta == 86400,
657        "expected dwExemptDelta 864000, got %d\n",
658        lpCacheEntryInfo->dwExemptDelta);
659     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
660     if (pDeleteUrlCacheEntryA)
661     {
662         ret = pDeleteUrlCacheEntryA(TEST_URL);
663         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
664         /* When explicitly deleting the cache entry, the file is also deleted */
665         todo_wine
666         check_file_not_exists(filenameA);
667     }
668     /* Test once again, setting the exempt delta via SetUrlCacheEntryInfo */
669     ret = CreateUrlCacheEntry(TEST_URL, 0, "html", filenameA, 0);
670     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
671     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
672     ret = CommitUrlCacheEntry(TEST_URL, filenameA, filetime_zero, filetime_zero,
673             NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY,
674             (LPBYTE)ok_header, strlen(ok_header), "html", NULL);
675     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
676     cbCacheEntryInfo = 0;
677     SetLastError(0xdeadbeef);
678     ret = GetUrlCacheEntryInfo(TEST_URL, NULL, &cbCacheEntryInfo);
679     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
680     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
681        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
682     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
683     ret = GetUrlCacheEntryInfo(TEST_URL, lpCacheEntryInfo, &cbCacheEntryInfo);
684     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
685     ok(lpCacheEntryInfo->CacheEntryType & (NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY),
686        "expected cache entry type NORMAL_CACHE_ENTRY | STICKY_CACHE_ENTRY, got %d (0x%08x)\n",
687        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
688     ok(lpCacheEntryInfo->dwExemptDelta == 86400,
689        "expected dwExemptDelta 864000, got %d\n",
690        lpCacheEntryInfo->dwExemptDelta);
691     lpCacheEntryInfo->dwExemptDelta = 0;
692     ret = SetUrlCacheEntryInfoA(TEST_URL, lpCacheEntryInfo,
693             CACHE_ENTRY_EXEMPT_DELTA_FC);
694     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
695     ret = GetUrlCacheEntryInfo(TEST_URL, lpCacheEntryInfo, &cbCacheEntryInfo);
696     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
697     ok(!lpCacheEntryInfo->dwExemptDelta, "expected dwExemptDelta 0, got %d\n",
698        lpCacheEntryInfo->dwExemptDelta);
699     /* See whether a sticky cache entry has the flag cleared once the exempt
700      * delta is meaningless.
701      */
702     ok(lpCacheEntryInfo->CacheEntryType & (NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY),
703        "expected cache entry type NORMAL_CACHE_ENTRY | STICKY_CACHE_ENTRY, got %d (0x%08x)\n",
704        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
705     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
706     if (pDeleteUrlCacheEntryA)
707     {
708         ret = pDeleteUrlCacheEntryA(TEST_URL);
709         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
710         todo_wine
711         check_file_not_exists(filenameA);
712     }
713 }
714
715 static void test_FindCloseUrlCache(void)
716 {
717     BOOL r;
718     DWORD err;
719
720     SetLastError(0xdeadbeef);
721     r = FindCloseUrlCache(NULL);
722     err = GetLastError();
723     ok(0 == r, "expected 0, got %d\n", r);
724     ok(ERROR_INVALID_HANDLE == err, "expected %d, got %d\n", ERROR_INVALID_HANDLE, err);
725 }
726
727 static void test_GetDiskInfoA(void)
728 {
729     BOOL ret;
730     DWORD error, cluster_size;
731     DWORDLONG free, total;
732     char path[MAX_PATH], *p;
733
734     GetSystemDirectoryA(path, MAX_PATH);
735     if ((p = strchr(path, '\\'))) *++p = 0;
736
737     ret = GetDiskInfoA(path, &cluster_size, &free, &total);
738     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
739
740     ret = GetDiskInfoA(path, &cluster_size, &free, NULL);
741     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
742
743     ret = GetDiskInfoA(path, &cluster_size, NULL, NULL);
744     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
745
746     ret = GetDiskInfoA(path, NULL, NULL, NULL);
747     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
748
749     SetLastError(0xdeadbeef);
750     strcpy(p, "\\non\\existing\\path");
751     ret = GetDiskInfoA(path, NULL, NULL, NULL);
752     error = GetLastError();
753     ok(!ret ||
754        broken(ret), /* < IE7 */
755        "GetDiskInfoA succeeded\n");
756     ok(error == ERROR_PATH_NOT_FOUND ||
757        broken(error == 0xdeadbeef), /* < IE7 */
758        "got %u expected ERROR_PATH_NOT_FOUND\n", error);
759
760     SetLastError(0xdeadbeef);
761     ret = GetDiskInfoA(NULL, NULL, NULL, NULL);
762     error = GetLastError();
763     ok(!ret, "GetDiskInfoA succeeded\n");
764     ok(error == ERROR_INVALID_PARAMETER, "got %u expected ERROR_INVALID_PARAMETER\n", error);
765 }
766
767 START_TEST(urlcache)
768 {
769     HMODULE hdll;
770     hdll = GetModuleHandleA("wininet.dll");
771
772     if(!GetProcAddress(hdll, "InternetGetCookieExW")) {
773         win_skip("Too old IE (older than 6.0)\n");
774         return;
775     }
776
777     pDeleteUrlCacheEntryA = (void*)GetProcAddress(hdll, "DeleteUrlCacheEntryA");
778     pUnlockUrlCacheEntryFileA = (void*)GetProcAddress(hdll, "UnlockUrlCacheEntryFileA");
779     test_urlcacheA();
780     test_FindCloseUrlCache();
781     test_GetDiskInfoA();
782 }