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 "winnls.h"
28 #include "wininet.h"
29 #include "winineti.h"
30 
31 #include "wine/test.h"
32 
33 static const char test_url[] = "http://urlcachetest.winehq.org/index.html";
34 static const WCHAR test_urlW[] = {'h','t','t','p',':','/','/','u','r','l','c','a','c','h','e','t','e','s','t','.',
35     'w','i','n','e','h','q','.','o','r','g','/','i','n','d','e','x','.','h','t','m','l',0};
36 static const char test_url1[] = "Visited: user@http://urlcachetest.winehq.org/index.html";
37 static const char test_hash_collisions1[] = "Visited: http://winehq.org/doc0.html";
38 static const char test_hash_collisions2[] = "Visited: http://winehq.org/doc75651909.html";
39 
40 static BOOL (WINAPI *pDeleteUrlCacheEntryA)(LPCSTR);
41 static BOOL (WINAPI *pUnlockUrlCacheEntryFileA)(LPCSTR,DWORD);
42 
43 static char filenameA[MAX_PATH + 1];
44 static char filenameA1[MAX_PATH + 1];
45 static BOOL old_ie = FALSE;
46 static BOOL ie10_cache = FALSE;
47 
48 static void check_cache_entry_infoA(const char *returnedfrom, INTERNET_CACHE_ENTRY_INFOA *lpCacheEntryInfo)
49 {
50     ok(lpCacheEntryInfo->dwStructSize == sizeof(*lpCacheEntryInfo), "%s: dwStructSize was %d\n", returnedfrom, lpCacheEntryInfo->dwStructSize);
51     ok(!strcmp(lpCacheEntryInfo->lpszSourceUrlName, test_url), "%s: lpszSourceUrlName should be %s instead of %s\n", returnedfrom, test_url, lpCacheEntryInfo->lpszSourceUrlName);
52     ok(!strcmp(lpCacheEntryInfo->lpszLocalFileName, filenameA), "%s: lpszLocalFileName should be %s instead of %s\n", returnedfrom, filenameA, lpCacheEntryInfo->lpszLocalFileName);
53     ok(!strcmp(lpCacheEntryInfo->lpszFileExtension, "html"), "%s: lpszFileExtension should be html instead of %s\n", returnedfrom, lpCacheEntryInfo->lpszFileExtension);
54 }
55 
56 static void test_find_url_cache_entriesA(void)
57 {
58     BOOL ret;
59     HANDLE hEnumHandle;
60     BOOL found = FALSE;
61     DWORD cbCacheEntryInfo;
62     DWORD cbCacheEntryInfoSaved;
63     INTERNET_CACHE_ENTRY_INFOA *lpCacheEntryInfo;
64 
65     cbCacheEntryInfo = 0;
66     SetLastError(0xdeadbeef);
67     hEnumHandle = FindFirstUrlCacheEntryA(NULL, NULL, &cbCacheEntryInfo);
68     ok(!hEnumHandle, "FindFirstUrlCacheEntry should have failed\n");
69     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER, "FindFirstUrlCacheEntry should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
70     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo * sizeof(char));
71     cbCacheEntryInfoSaved = cbCacheEntryInfo;
72     hEnumHandle = FindFirstUrlCacheEntryA(NULL, lpCacheEntryInfo, &cbCacheEntryInfo);
73     ok(hEnumHandle != NULL, "FindFirstUrlCacheEntry failed with error %d\n", GetLastError());
74     while (TRUE)
75     {
76         if (!strcmp(lpCacheEntryInfo->lpszSourceUrlName, test_url))
77         {
78             found = TRUE;
79             ret = TRUE;
80             break;
81         }
82         SetLastError(0xdeadbeef);
83         cbCacheEntryInfo = cbCacheEntryInfoSaved;
84         ret = FindNextUrlCacheEntryA(hEnumHandle, lpCacheEntryInfo, &cbCacheEntryInfo);
85         if (!ret)
86         {
87             if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
88             {
89                 lpCacheEntryInfo = HeapReAlloc(GetProcessHeap(), 0, lpCacheEntryInfo, cbCacheEntryInfo);
90                 cbCacheEntryInfoSaved = cbCacheEntryInfo;
91                 ret = FindNextUrlCacheEntryA(hEnumHandle, lpCacheEntryInfo, &cbCacheEntryInfo);
92             }
93         }
94         if (!ret)
95             break;
96     }
97     ok(ret, "FindNextUrlCacheEntry failed with error %d\n", GetLastError());
98     ok(found, "Committed url cache entry not found during enumeration\n");
99 
100     ret = FindCloseUrlCache(hEnumHandle);
101     ok(ret, "FindCloseUrlCache failed with error %d\n", GetLastError());
102     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
103 }
104 
105 static void test_GetUrlCacheEntryInfoExA(void)
106 {
107     BOOL ret;
108     DWORD cbCacheEntryInfo, cbRedirectUrl;
109     INTERNET_CACHE_ENTRY_INFOA *lpCacheEntryInfo;
110 
111     SetLastError(0xdeadbeef);
112     ret = GetUrlCacheEntryInfoExA(NULL, NULL, NULL, NULL, NULL, NULL, 0);
113     ok(!ret, "GetUrlCacheEntryInfoEx with NULL URL and NULL args should have failed\n");
114     ok(GetLastError() == ERROR_INVALID_PARAMETER,
115        "GetUrlCacheEntryInfoEx with NULL URL and NULL args should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
116 
117     cbCacheEntryInfo = sizeof(INTERNET_CACHE_ENTRY_INFOA);
118     SetLastError(0xdeadbeef);
119     ret = GetUrlCacheEntryInfoExA("", NULL, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
120     ok(!ret, "GetUrlCacheEntryInfoEx with zero-length buffer should fail\n");
121     ok(GetLastError() == ERROR_FILE_NOT_FOUND,
122        "GetUrlCacheEntryInfoEx should have set last error to ERROR_FILE_NOT_FOUND instead of %d\n", GetLastError());
123 
124     ret = GetUrlCacheEntryInfoExA(test_url, NULL, NULL, NULL, NULL, NULL, 0);
125     ok(ret, "GetUrlCacheEntryInfoEx with NULL args failed with error %d\n", GetLastError());
126 
127     cbCacheEntryInfo = 0;
128     SetLastError(0xdeadbeef);
129     ret = GetUrlCacheEntryInfoExA(test_url, NULL, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
130     ok(!ret, "GetUrlCacheEntryInfoEx with zero-length buffer should fail\n");
131     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
132        "GetUrlCacheEntryInfoEx should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
133 
134     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
135 
136     SetLastError(0xdeadbeef);
137     ret = GetUrlCacheEntryInfoExA(test_url, NULL, NULL, NULL, NULL, NULL, 0x200 /*GET_INSTALLED_ENTRY*/);
138     ok(ret == ie10_cache, "GetUrlCacheEntryInfoEx returned %x\n", ret);
139     if (!ret) ok(GetLastError() == ERROR_FILE_NOT_FOUND,
140             "GetUrlCacheEntryInfoEx should have set last error to ERROR_FILE_NOT_FOUND instead of %d\n", GetLastError());
141 
142     /* Unicode version of function seems to ignore 0x200 flag */
143     ret = GetUrlCacheEntryInfoExW(test_urlW, NULL, NULL, NULL, NULL, NULL, 0x200 /*GET_INSTALLED_ENTRY*/);
144     ok(ret || broken(old_ie && !ret), "GetUrlCacheEntryInfoExW failed with error %d\n", GetLastError());
145 
146     ret = GetUrlCacheEntryInfoExA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
147     ok(ret, "GetUrlCacheEntryInfoEx failed with error %d\n", GetLastError());
148 
149     if (ret) check_cache_entry_infoA("GetUrlCacheEntryInfoEx", lpCacheEntryInfo);
150 
151     lpCacheEntryInfo->CacheEntryType |= 0x10000000; /* INSTALLED_CACHE_ENTRY */
152     ret = SetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo, CACHE_ENTRY_ATTRIBUTE_FC);
153     ok(ret, "SetUrlCacheEntryInfoA failed with error %d\n", GetLastError());
154 
155     SetLastError(0xdeadbeef);
156     ret = GetUrlCacheEntryInfoExA(test_url, NULL, NULL, NULL, NULL, NULL, 0x200 /*GET_INSTALLED_ENTRY*/);
157     ok(ret, "GetUrlCacheEntryInfoEx failed with error %d\n", GetLastError());
158 
159     cbCacheEntryInfo = 100000;
160     SetLastError(0xdeadbeef);
161     ret = GetUrlCacheEntryInfoExA(test_url, NULL, &cbCacheEntryInfo, NULL, NULL, NULL, 0);
162     ok(!ret, "GetUrlCacheEntryInfoEx with zero-length buffer should fail\n");
163     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER, "GetUrlCacheEntryInfoEx should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
164 
165     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
166 
167     /* Querying the redirect URL fails with ERROR_INVALID_PARAMETER */
168     SetLastError(0xdeadbeef);
169     ret = GetUrlCacheEntryInfoExA(test_url, NULL, NULL, NULL, &cbRedirectUrl, NULL, 0);
170     ok(!ret, "GetUrlCacheEntryInfoEx should have failed\n");
171     ok(GetLastError() == ERROR_INVALID_PARAMETER,
172        "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
173     SetLastError(0xdeadbeef);
174     ret = GetUrlCacheEntryInfoExA(test_url, NULL, &cbCacheEntryInfo, NULL, &cbRedirectUrl, NULL, 0);
175     ok(!ret, "GetUrlCacheEntryInfoEx should have failed\n");
176     ok(GetLastError() == ERROR_INVALID_PARAMETER,
177        "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
178 }
179 
180 static void test_RetrieveUrlCacheEntryA(void)
181 {
182     BOOL ret;
183     DWORD cbCacheEntryInfo;
184 
185     cbCacheEntryInfo = 0;
186     SetLastError(0xdeadbeef);
187     ret = RetrieveUrlCacheEntryFileA(NULL, NULL, &cbCacheEntryInfo, 0);
188     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
189     ok(GetLastError() == ERROR_INVALID_PARAMETER, "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
190 
191     if (0)
192     {
193         /* Crashes on Win9x, NT4 and W2K */
194         SetLastError(0xdeadbeef);
195         ret = RetrieveUrlCacheEntryFileA(test_url, NULL, NULL, 0);
196         ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
197         ok(GetLastError() == ERROR_INVALID_PARAMETER, "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
198     }
199 
200     SetLastError(0xdeadbeef);
201     cbCacheEntryInfo = 100000;
202     ret = RetrieveUrlCacheEntryFileA(NULL, NULL, &cbCacheEntryInfo, 0);
203     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
204     ok(GetLastError() == ERROR_INVALID_PARAMETER, "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_PARAMETER instead of %d\n", GetLastError());
205 }
206 
207 static void test_IsUrlCacheEntryExpiredA(void)
208 {
209     static const char uncached_url[] =
210         "What's the airspeed velocity of an unladen swallow?";
211     BOOL ret;
212     FILETIME ft;
213     DWORD size;
214     INTERNET_CACHE_ENTRY_INFOA *info;
215     ULARGE_INTEGER exp_time;
216 
217     /* The function returns TRUE when the output time is NULL or the tested URL
218      * is NULL.
219      */
220     ret = IsUrlCacheEntryExpiredA(NULL, 0, NULL);
221     ok(ret != ie10_cache, "IsUrlCacheEntryExpiredA returned %x\n", ret);
222     ft.dwLowDateTime = 0xdeadbeef;
223     ft.dwHighDateTime = 0xbaadf00d;
224     ret = IsUrlCacheEntryExpiredA(NULL, 0, &ft);
225     ok(ret != ie10_cache, "IsUrlCacheEntryExpiredA returned %x\n", ret);
226     ok(ft.dwLowDateTime == 0xdeadbeef && ft.dwHighDateTime == 0xbaadf00d,
227        "expected time to be unchanged, got (%u,%u)\n",
228        ft.dwLowDateTime, ft.dwHighDateTime);
229     ret = IsUrlCacheEntryExpiredA(test_url, 0, NULL);
230     ok(ret != ie10_cache, "IsUrlCacheEntryExpiredA returned %x\n", ret);
231 
232     /* The return value should indicate whether the URL is expired,
233      * and the filetime indicates the last modified time, but a cache entry
234      * with a zero expire time is "not expired".
235      */
236     ft.dwLowDateTime = 0xdeadbeef;
237     ft.dwHighDateTime = 0xbaadf00d;
238     ret = IsUrlCacheEntryExpiredA(test_url, 0, &ft);
239     ok(!ret, "expected FALSE\n");
240     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
241        "expected time (0,0), got (%u,%u)\n",
242        ft.dwLowDateTime, ft.dwHighDateTime);
243 
244     /* Same behavior with bogus flags. */
245     ft.dwLowDateTime = 0xdeadbeef;
246     ft.dwHighDateTime = 0xbaadf00d;
247     ret = IsUrlCacheEntryExpiredA(test_url, 0xffffffff, &ft);
248     ok(!ret, "expected FALSE\n");
249     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
250        "expected time (0,0), got (%u,%u)\n",
251        ft.dwLowDateTime, ft.dwHighDateTime);
252 
253     /* Set the expire time to a point in the past.. */
254     ret = GetUrlCacheEntryInfoA(test_url, NULL, &size);
255     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
256     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
257        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
258     info = HeapAlloc(GetProcessHeap(), 0, size);
259     ret = GetUrlCacheEntryInfoA(test_url, info, &size);
260     ok(ret, "GetUrlCacheEntryInfo failed: %d\n", GetLastError());
261     GetSystemTimeAsFileTime(&info->ExpireTime);
262     exp_time.u.LowPart = info->ExpireTime.dwLowDateTime;
263     exp_time.u.HighPart = info->ExpireTime.dwHighDateTime;
264     exp_time.QuadPart -= 10 * 60 * (ULONGLONG)10000000;
265     info->ExpireTime.dwLowDateTime = exp_time.u.LowPart;
266     info->ExpireTime.dwHighDateTime = exp_time.u.HighPart;
267     ret = SetUrlCacheEntryInfoA(test_url, info, CACHE_ENTRY_EXPTIME_FC);
268     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
269     ft.dwLowDateTime = 0xdeadbeef;
270     ft.dwHighDateTime = 0xbaadf00d;
271     /* and the entry should be expired. */
272     ret = IsUrlCacheEntryExpiredA(test_url, 0, &ft);
273     ok(ret, "expected TRUE\n");
274     /* The modified time returned is 0. */
275     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
276        "expected time (0,0), got (%u,%u)\n",
277        ft.dwLowDateTime, ft.dwHighDateTime);
278     /* Set the expire time to a point in the future.. */
279     exp_time.QuadPart += 20 * 60 * (ULONGLONG)10000000;
280     info->ExpireTime.dwLowDateTime = exp_time.u.LowPart;
281     info->ExpireTime.dwHighDateTime = exp_time.u.HighPart;
282     ret = SetUrlCacheEntryInfoA(test_url, info, CACHE_ENTRY_EXPTIME_FC);
283     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
284     ft.dwLowDateTime = 0xdeadbeef;
285     ft.dwHighDateTime = 0xbaadf00d;
286     /* and the entry should no longer be expired. */
287     ret = IsUrlCacheEntryExpiredA(test_url, 0, &ft);
288     ok(!ret, "expected FALSE\n");
289     /* The modified time returned is still 0. */
290     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
291        "expected time (0,0), got (%u,%u)\n",
292        ft.dwLowDateTime, ft.dwHighDateTime);
293     /* Set the modified time... */
294     GetSystemTimeAsFileTime(&info->LastModifiedTime);
295     ret = SetUrlCacheEntryInfoA(test_url, info, CACHE_ENTRY_MODTIME_FC);
296     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
297     /* and the entry should still be unexpired.. */
298     ret = IsUrlCacheEntryExpiredA(test_url, 0, &ft);
299     ok(!ret, "expected FALSE\n");
300     /* but the modified time returned is the last modified time just set. */
301     ok(ft.dwLowDateTime == info->LastModifiedTime.dwLowDateTime &&
302        ft.dwHighDateTime == info->LastModifiedTime.dwHighDateTime,
303        "expected time (%u,%u), got (%u,%u)\n",
304        info->LastModifiedTime.dwLowDateTime,
305        info->LastModifiedTime.dwHighDateTime,
306        ft.dwLowDateTime, ft.dwHighDateTime);
307     HeapFree(GetProcessHeap(), 0, info);
308 
309     /* An uncached URL is implicitly expired, but with unknown time. */
310     ft.dwLowDateTime = 0xdeadbeef;
311     ft.dwHighDateTime = 0xbaadf00d;
312     ret = IsUrlCacheEntryExpiredA(uncached_url, 0, &ft);
313     ok(ret != ie10_cache, "IsUrlCacheEntryExpiredA returned %x\n", ret);
314     ok(!ft.dwLowDateTime && !ft.dwHighDateTime,
315        "expected time (0,0), got (%u,%u)\n",
316        ft.dwLowDateTime, ft.dwHighDateTime);
317 }
318 
319 static void _check_file_exists(LONG l, LPCSTR filename)
320 {
321     HANDLE file;
322 
323     file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
324                        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
325     ok_(__FILE__,l)(file != INVALID_HANDLE_VALUE,
326                     "expected file to exist, CreateFile failed with error %d\n",
327                     GetLastError());
328     CloseHandle(file);
329 }
330 
331 #define check_file_exists(f) _check_file_exists(__LINE__, f)
332 
333 static void _check_file_not_exists(LONG l, LPCSTR filename)
334 {
335     HANDLE file;
336 
337     file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
338                        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
339     ok_(__FILE__,l)(file == INVALID_HANDLE_VALUE,
340                     "expected file not to exist\n");
341     if (file != INVALID_HANDLE_VALUE)
342         CloseHandle(file);
343 }
344 
345 #define check_file_not_exists(f) _check_file_not_exists(__LINE__, f)
346 
347 static void create_and_write_file(LPCSTR filename, void *data, DWORD len)
348 {
349     HANDLE file;
350     DWORD written;
351     BOOL ret;
352 
353     file = CreateFileA(filename, GENERIC_WRITE,
354                        FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
355                        FILE_ATTRIBUTE_NORMAL, NULL);
356     ok(file != INVALID_HANDLE_VALUE, "CreateFileA failed with error %d\n", GetLastError());
357 
358     ret = WriteFile(file, data, len, &written, NULL);
359     ok(ret, "WriteFile failed with error %d\n", GetLastError());
360 
361     CloseHandle(file);
362 }
363 
364 static void test_urlcacheA(void)
365 {
366     static char long_url[300] = "http://www.winehq.org/";
367     static char ok_header[] = "HTTP/1.0 200 OK\r\n\r\n";
368     BOOL ret;
369     HANDLE hFile;
370     BYTE zero_byte = 0;
371     INTERNET_CACHE_ENTRY_INFOA *lpCacheEntryInfo;
372     INTERNET_CACHE_ENTRY_INFOA *lpCacheEntryInfo2;
373     DWORD cbCacheEntryInfo;
374     static const FILETIME filetime_zero;
375     FILETIME now;
376     int len;
377 
378     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA, 0);
379     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
380 
381     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA1, 0);
382     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
383     check_file_exists(filenameA1);
384     DeleteFileA(filenameA1);
385 
386     ok(lstrcmpiA(filenameA, filenameA1), "expected a different file name\n");
387 
388     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
389 
390     ret = CommitUrlCacheEntryA(test_url1, NULL, filetime_zero, filetime_zero, NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
391     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
392     cbCacheEntryInfo = 0;
393     ret = GetUrlCacheEntryInfoA(test_url1, NULL, &cbCacheEntryInfo);
394     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
395     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
396        "GetUrlCacheEntryInfo should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
397     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
398     ret = GetUrlCacheEntryInfoA(test_url1, lpCacheEntryInfo, &cbCacheEntryInfo);
399     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
400     ok(!memcmp(&lpCacheEntryInfo->ExpireTime, &filetime_zero, sizeof(FILETIME)),
401        "expected zero ExpireTime\n");
402     ok(!memcmp(&lpCacheEntryInfo->LastModifiedTime, &filetime_zero, sizeof(FILETIME)),
403        "expected zero LastModifiedTime\n");
404     ok(lpCacheEntryInfo->CacheEntryType == (NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY) ||
405        broken(lpCacheEntryInfo->CacheEntryType == NORMAL_CACHE_ENTRY /* NT4/W2k */),
406        "expected type NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY, got %08x\n",
407        lpCacheEntryInfo->CacheEntryType);
408     ok(!U(*lpCacheEntryInfo).dwExemptDelta, "expected dwExemptDelta 0, got %d\n",
409        U(*lpCacheEntryInfo).dwExemptDelta);
410 
411     /* Make sure there is a notable change in timestamps */
412     Sleep(1000);
413 
414     /* A subsequent commit with a different time/type doesn't change most of the entry */
415     GetSystemTimeAsFileTime(&now);
416     ret = CommitUrlCacheEntryA(test_url1, NULL, now, now, NORMAL_CACHE_ENTRY,
417             (LPBYTE)ok_header, strlen(ok_header), NULL, NULL);
418     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
419     cbCacheEntryInfo = 0;
420     ret = GetUrlCacheEntryInfoA(test_url1, NULL, &cbCacheEntryInfo);
421     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
422     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
423        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
424     lpCacheEntryInfo2 = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
425     ret = GetUrlCacheEntryInfoA(test_url1, lpCacheEntryInfo2, &cbCacheEntryInfo);
426     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
427     /* but it does change the time.. */
428     ok(memcmp(&lpCacheEntryInfo2->ExpireTime, &filetime_zero, sizeof(FILETIME)),
429        "expected positive ExpireTime\n");
430     ok(memcmp(&lpCacheEntryInfo2->LastModifiedTime, &filetime_zero, sizeof(FILETIME)),
431        "expected positive LastModifiedTime\n");
432     ok(lpCacheEntryInfo2->CacheEntryType == (NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY) ||
433        broken(lpCacheEntryInfo2->CacheEntryType == NORMAL_CACHE_ENTRY /* NT4/W2k */),
434        "expected type NORMAL_CACHE_ENTRY|URLHISTORY_CACHE_ENTRY, got %08x\n",
435        lpCacheEntryInfo2->CacheEntryType);
436     /* and set the headers. */
437     ok(lpCacheEntryInfo2->dwHeaderInfoSize == 19,
438         "expected headers size 19, got %d\n",
439         lpCacheEntryInfo2->dwHeaderInfoSize);
440     /* Hit rate gets incremented by 1 */
441     ok((lpCacheEntryInfo->dwHitRate + 1) == lpCacheEntryInfo2->dwHitRate,
442         "HitRate not incremented by one on commit\n");
443     /* Last access time should be updated */
444     ok(!(lpCacheEntryInfo->LastAccessTime.dwHighDateTime == lpCacheEntryInfo2->LastAccessTime.dwHighDateTime &&
445         lpCacheEntryInfo->LastAccessTime.dwLowDateTime == lpCacheEntryInfo2->LastAccessTime.dwLowDateTime),
446         "Last accessed time was not updated by commit\n");
447     /* File extension should be unset */
448     ok(lpCacheEntryInfo2->lpszFileExtension == NULL,
449         "Fileextension isn't unset: %s\n",
450         lpCacheEntryInfo2->lpszFileExtension);
451     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
452     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo2);
453 
454     ret = CommitUrlCacheEntryA(test_url, filenameA, filetime_zero, filetime_zero, NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
455     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
456 
457     cbCacheEntryInfo = 0;
458     SetLastError(0xdeadbeef);
459     ret = RetrieveUrlCacheEntryFileA(test_url, NULL, &cbCacheEntryInfo, 0);
460     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
461     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
462        "RetrieveUrlCacheEntryFile should have set last error to ERROR_INSUFFICIENT_BUFFER instead of %d\n", GetLastError());
463 
464     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
465     ret = RetrieveUrlCacheEntryFileA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo, 0);
466     ok(ret, "RetrieveUrlCacheEntryFile failed with error %d\n", GetLastError());
467 
468     if (ret) check_cache_entry_infoA("RetrieveUrlCacheEntryFile", lpCacheEntryInfo);
469 
470     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
471 
472     cbCacheEntryInfo = 0;
473     SetLastError(0xdeadbeef);
474     ret = RetrieveUrlCacheEntryFileA(test_url1, NULL, &cbCacheEntryInfo, 0);
475     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
476     ok(GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_INSUFFICIENT_BUFFER,
477        "RetrieveUrlCacheEntryFile should have set last error to ERROR_INVALID_DATA instead of %d\n", GetLastError());
478 
479     if (pUnlockUrlCacheEntryFileA)
480     {
481         ret = pUnlockUrlCacheEntryFileA(test_url, 0);
482         ok(ret, "UnlockUrlCacheEntryFileA failed with error %d\n", GetLastError());
483     }
484 
485     /* test Find*UrlCacheEntry functions */
486     test_find_url_cache_entriesA();
487 
488     test_GetUrlCacheEntryInfoExA();
489     test_RetrieveUrlCacheEntryA();
490     test_IsUrlCacheEntryExpiredA();
491 
492     if (pDeleteUrlCacheEntryA)
493     {
494         ret = pDeleteUrlCacheEntryA(test_url);
495         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
496         ret = pDeleteUrlCacheEntryA(test_url1);
497         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
498     }
499 
500     SetLastError(0xdeadbeef);
501     ret = DeleteFileA(filenameA);
502     ok(!ret && GetLastError() == ERROR_FILE_NOT_FOUND, "local file should no longer exist\n");
503 
504     /* Creating two entries with the same URL */
505     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA, 0);
506     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
507 
508     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA1, 0);
509     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
510 
511     ok(lstrcmpiA(filenameA, filenameA1), "expected a different file name\n");
512 
513     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
514     create_and_write_file(filenameA1, &zero_byte, sizeof(zero_byte));
515     check_file_exists(filenameA);
516     check_file_exists(filenameA1);
517 
518     ret = CommitUrlCacheEntryA(test_url, filenameA, filetime_zero,
519             filetime_zero, NORMAL_CACHE_ENTRY, (LPBYTE)ok_header,
520             strlen(ok_header), "html", NULL);
521     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
522     check_file_exists(filenameA);
523     check_file_exists(filenameA1);
524     ret = CommitUrlCacheEntryA(test_url, filenameA1, filetime_zero,
525             filetime_zero, COOKIE_CACHE_ENTRY, NULL, 0, "html", NULL);
526     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
527     /* By committing the same URL a second time, the prior entry is
528      * overwritten...
529      */
530     cbCacheEntryInfo = 0;
531     SetLastError(0xdeadbeef);
532     ret = GetUrlCacheEntryInfoA(test_url, NULL, &cbCacheEntryInfo);
533     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
534     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
535        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
536     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
537     ret = GetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo);
538     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
539     /* with the previous entry type retained.. */
540     ok(lpCacheEntryInfo->CacheEntryType & NORMAL_CACHE_ENTRY,
541        "expected cache entry type NORMAL_CACHE_ENTRY, got %d (0x%08x)\n",
542        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
543     /* and the headers overwritten.. */
544     ok(!lpCacheEntryInfo->dwHeaderInfoSize, "expected headers size 0, got %d\n",
545        lpCacheEntryInfo->dwHeaderInfoSize);
546     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
547     /* and the previous filename shouldn't exist. */
548     check_file_not_exists(filenameA);
549     check_file_exists(filenameA1);
550 
551     if (pDeleteUrlCacheEntryA)
552     {
553         ret = pDeleteUrlCacheEntryA(test_url);
554         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
555         check_file_not_exists(filenameA);
556         check_file_not_exists(filenameA1);
557         /* Just in case, clean up files */
558         DeleteFileA(filenameA1);
559         DeleteFileA(filenameA);
560     }
561 
562     /* Check whether a retrieved cache entry can be deleted before it's
563      * unlocked:
564      */
565     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA, 0);
566     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
567     ret = CommitUrlCacheEntryA(test_url, filenameA, filetime_zero, filetime_zero,
568             NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
569     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
570 
571     cbCacheEntryInfo = 0;
572     SetLastError(0xdeadbeef);
573     ret = RetrieveUrlCacheEntryFileA(test_url, NULL, &cbCacheEntryInfo, 0);
574     ok(!ret, "RetrieveUrlCacheEntryFile should have failed\n");
575     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
576        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
577 
578     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
579     ret = RetrieveUrlCacheEntryFileA(test_url, lpCacheEntryInfo,
580             &cbCacheEntryInfo, 0);
581     ok(ret, "RetrieveUrlCacheEntryFile failed with error %d\n", GetLastError());
582 
583     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
584 
585     if (pDeleteUrlCacheEntryA)
586     {
587         ret = pDeleteUrlCacheEntryA(test_url);
588         ok(!ret, "Expected failure\n");
589         ok(GetLastError() == ERROR_SHARING_VIOLATION,
590            "Expected ERROR_SHARING_VIOLATION, got %d\n", GetLastError());
591         check_file_exists(filenameA);
592     }
593 
594     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
595     memset(lpCacheEntryInfo, 0, cbCacheEntryInfo);
596     ret = GetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo);
597     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
598     ok(lpCacheEntryInfo->CacheEntryType & 0x400000,
599         "CacheEntryType hasn't PENDING_DELETE_CACHE_ENTRY set, (flags %08x)\n",
600         lpCacheEntryInfo->CacheEntryType);
601     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
602 
603     if (pUnlockUrlCacheEntryFileA)
604     {
605         check_file_exists(filenameA);
606         ret = pUnlockUrlCacheEntryFileA(test_url, 0);
607         ok(ret, "UnlockUrlCacheEntryFileA failed: %d\n", GetLastError());
608         /* By unlocking the already-deleted cache entry, the file associated
609          * with it is deleted..
610          */
611         check_file_not_exists(filenameA);
612         /* (just in case, delete file) */
613         DeleteFileA(filenameA);
614     }
615     if (pDeleteUrlCacheEntryA)
616     {
617         /* and a subsequent deletion should fail. */
618         ret = pDeleteUrlCacheEntryA(test_url);
619         ok(!ret, "Expected failure\n");
620         ok(GetLastError() == ERROR_FILE_NOT_FOUND,
621            "expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());
622     }
623 
624     /* Test whether preventing a file from being deleted causes
625      * DeleteUrlCacheEntryA to fail.
626      */
627     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA, 0);
628     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
629 
630     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
631     check_file_exists(filenameA);
632 
633     ret = CommitUrlCacheEntryA(test_url, filenameA, filetime_zero,
634             filetime_zero, NORMAL_CACHE_ENTRY, (LPBYTE)ok_header,
635             strlen(ok_header), "html", NULL);
636     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
637     check_file_exists(filenameA);
638     hFile = CreateFileA(filenameA, GENERIC_READ, 0, NULL, OPEN_EXISTING,
639             FILE_ATTRIBUTE_NORMAL, NULL);
640     ok(hFile != INVALID_HANDLE_VALUE, "CreateFileA failed: %d\n",
641        GetLastError());
642     if (pDeleteUrlCacheEntryA)
643     {
644         /* DeleteUrlCacheEntryA should succeed.. */
645         ret = pDeleteUrlCacheEntryA(test_url);
646         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
647     }
648     CloseHandle(hFile);
649     if (pDeleteUrlCacheEntryA)
650     {
651         /* and a subsequent deletion should fail.. */
652         ret = pDeleteUrlCacheEntryA(test_url);
653         ok(!ret, "Expected failure\n");
654         ok(GetLastError() == ERROR_FILE_NOT_FOUND,
655            "expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());
656     }
657     /* and the file should be untouched. */
658     check_file_exists(filenameA);
659     DeleteFileA(filenameA);
660 
661     /* Try creating a sticky entry.  Unlike non-sticky entries, the filename
662      * must have been set already.
663      */
664     SetLastError(0xdeadbeef);
665     ret = CommitUrlCacheEntryA(test_url, NULL, filetime_zero, filetime_zero,
666             STICKY_CACHE_ENTRY, (LPBYTE)ok_header, strlen(ok_header), "html",
667             NULL);
668     ok(ret == ie10_cache, "CommitUrlCacheEntryA returned %x\n", ret);
669     if (!ret) ok(GetLastError() == ERROR_INVALID_PARAMETER,
670             "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
671     SetLastError(0xdeadbeef);
672     ret = CommitUrlCacheEntryA(test_url, NULL, filetime_zero, filetime_zero,
673             NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY,
674             (LPBYTE)ok_header, strlen(ok_header), "html", NULL);
675     ok(ret == ie10_cache, "CommitUrlCacheEntryA returned %x\n", ret);
676     if (!ret) ok(GetLastError() == ERROR_INVALID_PARAMETER,
677             "expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());
678 
679     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA, 0);
680     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
681     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
682     ret = CommitUrlCacheEntryA(test_url, filenameA, filetime_zero, filetime_zero,
683             NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY,
684             (LPBYTE)ok_header, strlen(ok_header), "html", NULL);
685     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
686     cbCacheEntryInfo = 0;
687     SetLastError(0xdeadbeef);
688     ret = GetUrlCacheEntryInfoA(test_url, NULL, &cbCacheEntryInfo);
689     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
690     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
691        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
692     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
693     ret = GetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo);
694     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
695     ok(lpCacheEntryInfo->CacheEntryType & (NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY),
696        "expected cache entry type NORMAL_CACHE_ENTRY | STICKY_CACHE_ENTRY, got %d (0x%08x)\n",
697        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
698     ok(U(*lpCacheEntryInfo).dwExemptDelta == 86400,
699        "expected dwExemptDelta 86400, got %d\n",
700        U(*lpCacheEntryInfo).dwExemptDelta);
701     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
702     if (pDeleteUrlCacheEntryA)
703     {
704         ret = pDeleteUrlCacheEntryA(test_url);
705         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
706         /* When explicitly deleting the cache entry, the file is also deleted */
707         check_file_not_exists(filenameA);
708     }
709     /* Test once again, setting the exempt delta via SetUrlCacheEntryInfo */
710     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA, 0);
711     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
712     create_and_write_file(filenameA, &zero_byte, sizeof(zero_byte));
713     ret = CommitUrlCacheEntryA(test_url, filenameA, filetime_zero, filetime_zero,
714             NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY,
715             (LPBYTE)ok_header, strlen(ok_header), "html", NULL);
716     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
717     cbCacheEntryInfo = 0;
718     SetLastError(0xdeadbeef);
719     ret = GetUrlCacheEntryInfoA(test_url, NULL, &cbCacheEntryInfo);
720     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
721     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
722        "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
723     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
724     ret = GetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo);
725     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
726     ok(lpCacheEntryInfo->CacheEntryType & (NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY),
727        "expected cache entry type NORMAL_CACHE_ENTRY | STICKY_CACHE_ENTRY, got %d (0x%08x)\n",
728        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
729     ok(U(*lpCacheEntryInfo).dwExemptDelta == 86400,
730        "expected dwExemptDelta 86400, got %d\n",
731        U(*lpCacheEntryInfo).dwExemptDelta);
732     U(*lpCacheEntryInfo).dwExemptDelta = 0;
733     ret = SetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo,
734             CACHE_ENTRY_EXEMPT_DELTA_FC);
735     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
736     ret = GetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo);
737     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
738     ok(!U(*lpCacheEntryInfo).dwExemptDelta, "expected dwExemptDelta 0, got %d\n",
739        U(*lpCacheEntryInfo).dwExemptDelta);
740     /* See whether a sticky cache entry has the flag cleared once the exempt
741      * delta is meaningless.
742      */
743     ok(lpCacheEntryInfo->CacheEntryType & (NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY),
744        "expected cache entry type NORMAL_CACHE_ENTRY | STICKY_CACHE_ENTRY, got %d (0x%08x)\n",
745        lpCacheEntryInfo->CacheEntryType, lpCacheEntryInfo->CacheEntryType);
746 
747     /* Recommit of Url entry keeps dwExemptDelta */
748     U(*lpCacheEntryInfo).dwExemptDelta = 8600;
749     ret = SetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo,
750             CACHE_ENTRY_EXEMPT_DELTA_FC);
751     ok(ret, "SetUrlCacheEntryInfo failed: %d\n", GetLastError());
752 
753     ret = CreateUrlCacheEntryA(test_url, 0, "html", filenameA1, 0);
754     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
755     create_and_write_file(filenameA1, &zero_byte, sizeof(zero_byte));
756 
757     ret = CommitUrlCacheEntryA(test_url, filenameA1, filetime_zero, filetime_zero,
758             NORMAL_CACHE_ENTRY|STICKY_CACHE_ENTRY,
759             (LPBYTE)ok_header, strlen(ok_header), "html", NULL);
760     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
761 
762     ret = GetUrlCacheEntryInfoA(test_url, lpCacheEntryInfo, &cbCacheEntryInfo);
763     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
764     ok(U(*lpCacheEntryInfo).dwExemptDelta == 8600 || (ie10_cache && U(*lpCacheEntryInfo).dwExemptDelta == 86400),
765        "expected dwExemptDelta 8600, got %d\n", U(*lpCacheEntryInfo).dwExemptDelta);
766 
767     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
768 
769     if (pDeleteUrlCacheEntryA)
770     {
771         ret = pDeleteUrlCacheEntryA(test_url);
772         ok(ret, "DeleteUrlCacheEntryA failed with error %d\n", GetLastError());
773         check_file_not_exists(filenameA);
774     }
775 
776     /* Test if files with identical hash keys are handled correctly */
777     ret = CommitUrlCacheEntryA(test_hash_collisions1, NULL, filetime_zero, filetime_zero, NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
778     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
779     ret = CommitUrlCacheEntryA(test_hash_collisions2, NULL, filetime_zero, filetime_zero, NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
780     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
781 
782     cbCacheEntryInfo = 0;
783     ret = GetUrlCacheEntryInfoA(test_hash_collisions1, NULL, &cbCacheEntryInfo);
784     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
785     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
786             "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
787     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
788     ret = GetUrlCacheEntryInfoA(test_hash_collisions1, lpCacheEntryInfo, &cbCacheEntryInfo);
789     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
790     ok(!strcmp(lpCacheEntryInfo->lpszSourceUrlName, test_hash_collisions1),
791             "got incorrect entry: %s\n", lpCacheEntryInfo->lpszSourceUrlName);
792     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
793 
794     cbCacheEntryInfo = 0;
795     ret = GetUrlCacheEntryInfoA(test_hash_collisions2, NULL, &cbCacheEntryInfo);
796     ok(!ret, "GetUrlCacheEntryInfo should have failed\n");
797     ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
798             "expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
799     lpCacheEntryInfo = HeapAlloc(GetProcessHeap(), 0, cbCacheEntryInfo);
800     ret = GetUrlCacheEntryInfoA(test_hash_collisions2, lpCacheEntryInfo, &cbCacheEntryInfo);
801     ok(ret, "GetUrlCacheEntryInfo failed with error %d\n", GetLastError());
802     ok(!strcmp(lpCacheEntryInfo->lpszSourceUrlName, test_hash_collisions2),
803             "got incorrect entry: %s\n", lpCacheEntryInfo->lpszSourceUrlName);
804     HeapFree(GetProcessHeap(), 0, lpCacheEntryInfo);
805 
806     if (pDeleteUrlCacheEntryA) {
807         ret = pDeleteUrlCacheEntryA(test_hash_collisions1);
808         ok(ret, "DeleteUrlCacheEntry failed: %d\n", GetLastError());
809         ret = pDeleteUrlCacheEntryA(test_hash_collisions2);
810         ok(ret, "DeleteUrlCacheEntry failed: %d\n", GetLastError());
811     }
812 
813     len = strlen(long_url);
814     memset(long_url+len, 'a', sizeof(long_url)-len);
815     long_url[sizeof(long_url)-1] = 0;
816     ret = CreateUrlCacheEntryA(long_url, 0, NULL, filenameA, 0);
817     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
818     check_file_exists(filenameA);
819     DeleteFileA(filenameA);
820 
821     ret = CreateUrlCacheEntryA(long_url, 0, "extension", filenameA, 0);
822     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
823     check_file_exists(filenameA);
824     DeleteFileA(filenameA);
825 
826     long_url[250] = 0;
827     ret = CreateUrlCacheEntryA(long_url, 0, NULL, filenameA, 0);
828     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
829     check_file_exists(filenameA);
830     DeleteFileA(filenameA);
831 
832     ret = CreateUrlCacheEntryA(long_url, 0, "extension", filenameA, 0);
833     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
834     check_file_exists(filenameA);
835     DeleteFileA(filenameA);
836 }
837 
838 static void test_urlcacheW(void)
839 {
840     static struct test_data
841     {
842         DWORD err;
843         WCHAR url[128];
844         char encoded_url[128];
845         WCHAR extension[32];
846         WCHAR header_info[128];
847     }urls[] = {
848         {
849             0, {'h','t','t','p',':','/','/','T','.','p','l','/','t',0},
850             "http://T.pl/t", {0}, {0}
851         },
852         {
853             0, {'w','w','w','.','T','.','p','l','/','t',0},
854             "www.T.pl/t", {0}, {0}
855         },
856         {
857             0, {'h','t','t','p',':','/','/','w','w','w','.','t','e','s','t',0x15b,0x107,
858                 '.','o','r','g','/','t','e','s','t','.','h','t','m','l',0},
859             "http://www.xn--test-ota71c.org/test.html", {'t','x','t',0}, {0}
860         },
861         {
862             0, {'w','w','w','.','T','e','s','t',0x15b,0x107,'.','o','r','g',
863                 '/','t','e','s','t','.','h','t','m','l',0},
864             "www.Test\xc5\x9b\xc4\x87.org/test.html", {'a',0x106,'a',0}, {'b',0x106,'b',0}
865         },
866         {
867             0, {'H','t','t','p','s',':','/','/',0x15b,0x15b,0x107,'/','t',0x107,'/',
868                 't','e','s','t','?','a','=','%','2','0',0x106,0},
869             "Https://xn--4da1oa/t\xc4\x87/test?a=%20\xc4\x86", {'a',0x15b,'a',0}, {'b',0x15b,'b',0}
870         },
871         {
872             12005, {'h','t','t','p','s',':','/','/','/','/',0x107,'.','o','r','g','/','t','e','s','t',0},
873             "", {0}, {0}
874         },
875         {
876             0, {'C','o','o','k','i','e',':',' ','u','s','e','r','@','h','t','t','p',
877                 ':','/','/','t',0x15b,0x107,'.','o','r','g','/',0},
878             "Cookie: user@http://t\xc5\x9b\xc4\x87.org/", {0}, {0}
879         }
880     };
881     static const FILETIME filetime_zero;
882 
883     WCHAR bufW[MAX_PATH];
884     DWORD i;
885     BOOL ret;
886 
887     if(old_ie) {
888         win_skip("urlcache unicode functions\n");
889         return;
890     }
891 
892     if(ie10_cache) {
893         if(!MultiByteToWideChar(CP_ACP, 0, urls[6].encoded_url, -1,
894                     urls[6].url, sizeof(urls[6].url)/sizeof(WCHAR)))
895             urls[6].url[0] = 0;
896 
897         trace("converted url in test 6: %s\n", wine_dbgstr_w(urls[6].url));
898     }
899 
900     for(i=0; i<sizeof(urls)/sizeof(*urls); i++) {
901         INTERNET_CACHE_ENTRY_INFOA *entry_infoA;
902         INTERNET_CACHE_ENTRY_INFOW *entry_infoW;
903         DWORD size;
904 
905         if(!urls[i].url[0]) {
906             win_skip("No UTF16 version of url (%d)\n", i);
907             continue;
908         }
909 
910         SetLastError(0xdeadbeef);
911         ret = CreateUrlCacheEntryW(urls[i].url, 0, NULL, bufW, 0);
912         if(urls[i].err != 0) {
913             ok(!ret, "%d) CreateUrlCacheEntryW succeeded\n", i);
914             ok(urls[i].err == GetLastError(), "%d) GetLastError() = %d\n", i, GetLastError());
915             continue;
916         }
917         ok(ret, "%d) CreateUrlCacheEntryW failed: %d\n", i, GetLastError());
918 
919         /* dwHeaderSize is ignored, pass 0 to prove it */
920         ret = CommitUrlCacheEntryW(urls[i].url, bufW, filetime_zero, filetime_zero,
921                 NORMAL_CACHE_ENTRY, urls[i].header_info, 0, urls[i].extension, NULL);
922         ok(ret, "%d) CommitUrlCacheEntryW failed: %d\n", i, GetLastError());
923 
924         SetLastError(0xdeadbeef);
925         size = 0;
926         ret = GetUrlCacheEntryInfoW(urls[i].url, NULL, &size);
927         ok(!ret && GetLastError()==ERROR_INSUFFICIENT_BUFFER,
928                 "%d) GetLastError() = %d\n", i, GetLastError());
929         entry_infoW = HeapAlloc(GetProcessHeap(), 0, size);
930         ret = GetUrlCacheEntryInfoW(urls[i].url, entry_infoW, &size);
931         ok(ret, "%d) GetUrlCacheEntryInfoW failed: %d\n", i, GetLastError());
932 
933         ret = GetUrlCacheEntryInfoA(urls[i].encoded_url, NULL, &size);
934         ok(!ret && GetLastError()==ERROR_INSUFFICIENT_BUFFER,
935                 "%d) GetLastError() = %d\n", i, GetLastError());
936         if(!ret && GetLastError()!=ERROR_INSUFFICIENT_BUFFER) {
937             win_skip("ANSI version of url is incorrect\n");
938             continue;
939         }
940         entry_infoA = HeapAlloc(GetProcessHeap(), 0, size);
941         ret = GetUrlCacheEntryInfoA(urls[i].encoded_url, entry_infoA, &size);
942         ok(ret, "%d) GetUrlCacheEntryInfoA failed: %d\n", i, GetLastError());
943 
944         ok(entry_infoW->dwStructSize == entry_infoA->dwStructSize,
945                 "%d) entry_infoW->dwStructSize = %d, expected %d\n",
946                 i, entry_infoW->dwStructSize, entry_infoA->dwStructSize);
947         ok(!lstrcmpW(urls[i].url, entry_infoW->lpszSourceUrlName),
948                 "%d) entry_infoW->lpszSourceUrlName = %s\n",
949                 i, wine_dbgstr_w(entry_infoW->lpszSourceUrlName));
950         ok(!lstrcmpA(urls[i].encoded_url, entry_infoA->lpszSourceUrlName),
951                 "%d) entry_infoA->lpszSourceUrlName = %s\n",
952                 i, entry_infoA->lpszSourceUrlName);
953         ok(entry_infoW->CacheEntryType == entry_infoA->CacheEntryType,
954                 "%d) entry_infoW->CacheEntryType = %x, expected %x\n",
955                 i, entry_infoW->CacheEntryType, entry_infoA->CacheEntryType);
956         ok(entry_infoW->dwUseCount == entry_infoA->dwUseCount,
957                 "%d) entry_infoW->dwUseCount = %d, expected %d\n",
958                 i, entry_infoW->dwUseCount, entry_infoA->dwUseCount);
959         ok(entry_infoW->dwHitRate == entry_infoA->dwHitRate,
960                 "%d) entry_infoW->dwHitRate = %d, expected %d\n",
961                 i, entry_infoW->dwHitRate, entry_infoA->dwHitRate);
962         ok(entry_infoW->dwSizeLow == entry_infoA->dwSizeLow,
963                 "%d) entry_infoW->dwSizeLow = %d, expected %d\n",
964                 i, entry_infoW->dwSizeLow, entry_infoA->dwSizeLow);
965         ok(entry_infoW->dwSizeHigh == entry_infoA->dwSizeHigh,
966                 "%d) entry_infoW->dwSizeHigh = %d, expected %d\n",
967                 i, entry_infoW->dwSizeHigh, entry_infoA->dwSizeHigh);
968         ok(!memcmp(&entry_infoW->LastModifiedTime, &entry_infoA->LastModifiedTime, sizeof(FILETIME)),
969                 "%d) entry_infoW->LastModifiedTime is incorrect\n", i);
970         ok(!memcmp(&entry_infoW->ExpireTime, &entry_infoA->ExpireTime, sizeof(FILETIME)),
971                 "%d) entry_infoW->ExpireTime is incorrect\n", i);
972         ok(!memcmp(&entry_infoW->LastAccessTime, &entry_infoA->LastAccessTime, sizeof(FILETIME)),
973                 "%d) entry_infoW->LastAccessTime is incorrect\n", i);
974         ok(!memcmp(&entry_infoW->LastSyncTime, &entry_infoA->LastSyncTime, sizeof(FILETIME)),
975                 "%d) entry_infoW->LastSyncTime is incorrect\n", i);
976 
977         MultiByteToWideChar(CP_ACP, 0, entry_infoA->lpszLocalFileName, -1, bufW, MAX_PATH);
978         ok(!lstrcmpW(entry_infoW->lpszLocalFileName, bufW),
979                 "%d) entry_infoW->lpszLocalFileName = %s, expected %s\n",
980                 i, wine_dbgstr_w(entry_infoW->lpszLocalFileName), wine_dbgstr_w(bufW));
981 
982         if(!urls[i].header_info[0]) {
983             ok(!entry_infoW->lpHeaderInfo, "entry_infoW->lpHeaderInfo != NULL\n");
984         }else {
985             ok(!lstrcmpW((WCHAR*)entry_infoW->lpHeaderInfo, urls[i].header_info),
986                     "%d) entry_infoW->lpHeaderInfo = %s\n",
987                     i, wine_dbgstr_w((WCHAR*)entry_infoW->lpHeaderInfo));
988         }
989 
990         if(!urls[i].extension[0]) {
991             ok(!entry_infoW->lpszFileExtension || (ie10_cache && !entry_infoW->lpszFileExtension[0]),
992                     "%d) entry_infoW->lpszFileExtension = %s\n",
993                     i, wine_dbgstr_w(entry_infoW->lpszFileExtension));
994         }else {
995             MultiByteToWideChar(CP_ACP, 0, entry_infoA->lpszFileExtension, -1, bufW, MAX_PATH);
996             ok(!lstrcmpW(entry_infoW->lpszFileExtension, bufW) ||
997                     (ie10_cache && !lstrcmpW(entry_infoW->lpszFileExtension, urls[i].extension)),
998                     "%d) entry_infoW->lpszFileExtension = %s, expected %s\n",
999                     i, wine_dbgstr_w(entry_infoW->lpszFileExtension), wine_dbgstr_w(bufW));
1000         }
1001 
1002         HeapFree(GetProcessHeap(), 0, entry_infoW);
1003         HeapFree(GetProcessHeap(), 0, entry_infoA);
1004 
1005         if(pDeleteUrlCacheEntryA) {
1006             ret = pDeleteUrlCacheEntryA(urls[i].encoded_url);
1007             ok(ret, "%d) DeleteUrlCacheEntryW failed: %d\n", i, GetLastError());
1008         }
1009     }
1010 }
1011 
1012 static void test_FindCloseUrlCache(void)
1013 {
1014     BOOL r;
1015     DWORD err;
1016 
1017     SetLastError(0xdeadbeef);
1018     r = FindCloseUrlCache(NULL);
1019     err = GetLastError();
1020     ok(0 == r, "expected 0, got %d\n", r);
1021     ok(ERROR_INVALID_HANDLE == err, "expected %d, got %d\n", ERROR_INVALID_HANDLE, err);
1022 }
1023 
1024 static void test_GetDiskInfoA(void)
1025 {
1026     BOOL ret;
1027     DWORD error, cluster_size;
1028     DWORDLONG free, total;
1029     char path[MAX_PATH], *p;
1030 
1031     GetSystemDirectoryA(path, MAX_PATH);
1032     if ((p = strchr(path, '\\'))) *++p = 0;
1033 
1034     ret = GetDiskInfoA(path, &cluster_size, &free, &total);
1035     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
1036 
1037     ret = GetDiskInfoA(path, &cluster_size, &free, NULL);
1038     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
1039 
1040     ret = GetDiskInfoA(path, &cluster_size, NULL, NULL);
1041     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
1042 
1043     ret = GetDiskInfoA(path, NULL, NULL, NULL);
1044     ok(ret, "GetDiskInfoA failed %u\n", GetLastError());
1045 
1046     SetLastError(0xdeadbeef);
1047     strcpy(p, "\\non\\existing\\path");
1048     ret = GetDiskInfoA(path, NULL, NULL, NULL);
1049     error = GetLastError();
1050     ok(!ret ||
1051        broken(old_ie && ret), /* < IE7 */
1052        "GetDiskInfoA succeeded\n");
1053     ok(error == ERROR_PATH_NOT_FOUND ||
1054        broken(old_ie && error == 0xdeadbeef), /* < IE7 */
1055        "got %u expected ERROR_PATH_NOT_FOUND\n", error);
1056 
1057     SetLastError(0xdeadbeef);
1058     ret = GetDiskInfoA(NULL, NULL, NULL, NULL);
1059     error = GetLastError();
1060     ok(!ret, "GetDiskInfoA succeeded\n");
1061     ok(error == ERROR_INVALID_PARAMETER, "got %u expected ERROR_INVALID_PARAMETER\n", error);
1062 }
1063 
1064 static BOOL cache_entry_exists(const char *url)
1065 {
1066     static char buf[10000];
1067     DWORD size = sizeof(buf);
1068     BOOL ret;
1069 
1070     ret = GetUrlCacheEntryInfoA(url, (void*)buf, &size);
1071     ok(ret || GetLastError() == ERROR_FILE_NOT_FOUND, "GetUrlCacheEntryInfoA returned %x (%u)\n", ret, GetLastError());
1072 
1073     return ret;
1074 }
1075 
1076 static void test_trailing_slash(void)
1077 {
1078     char filename[MAX_PATH];
1079     BYTE zero_byte = 0;
1080     BOOL ret;
1081 
1082     static const FILETIME filetime_zero;
1083     static char url_with_slash[] = "http://testing.cache.com/";
1084 
1085 
1086     ret = CreateUrlCacheEntryA(url_with_slash, 0, "html", filename, 0);
1087     ok(ret, "CreateUrlCacheEntry failed with error %d\n", GetLastError());
1088 
1089     create_and_write_file(filename, &zero_byte, sizeof(zero_byte));
1090 
1091     ret = CommitUrlCacheEntryA("Visited: http://testing.cache.com/", NULL, filetime_zero, filetime_zero,
1092             NORMAL_CACHE_ENTRY, NULL, 0, "html", NULL);
1093     ok(ret, "CommitUrlCacheEntry failed with error %d\n", GetLastError());
1094 
1095     ok(cache_entry_exists("Visited: http://testing.cache.com/"), "cache entry does not exist\n");
1096     ok(!cache_entry_exists("Visited: http://testing.cache.com"), "cache entry exists\n");
1097 
1098     ret = DeleteUrlCacheEntryA("Visited: http://testing.cache.com/");
1099     ok(ret, "DeleteCacheEntryA failed\n");
1100     DeleteFileA(filename);
1101 }
1102 
1103 START_TEST(urlcache)
1104 {
1105     HMODULE hdll;
1106     hdll = GetModuleHandleA("wininet.dll");
1107 
1108     if(!GetProcAddress(hdll, "InternetGetCookieExW")) {
1109         win_skip("Too old IE (older than 6.0)\n");
1110         return;
1111     }
1112     if(!GetProcAddress(hdll, "InternetGetSecurityInfoByURL")) /* < IE7 */
1113         old_ie = TRUE;
1114 
1115     if(GetProcAddress(hdll, "CreateUrlCacheEntryExW")) {
1116         trace("Running tests on IE10 or newer\n");
1117         ie10_cache = TRUE;
1118     }
1119 
1120     pDeleteUrlCacheEntryA = (void*)GetProcAddress(hdll, "DeleteUrlCacheEntryA");
1121     pUnlockUrlCacheEntryFileA = (void*)GetProcAddress(hdll, "UnlockUrlCacheEntryFileA");
1122     test_urlcacheA();
1123     test_urlcacheW();
1124     test_FindCloseUrlCache();
1125     test_GetDiskInfoA();
1126     test_trailing_slash();
1127 }
1128