xref: /reactos/dll/win32/wininet/urlcache.c (revision 803b5e13)
1 /*
2  * Wininet - Url Cache functions
3  *
4  * Copyright 2001,2002 CodeWeavers
5  * Copyright 2003-2008 Robert Shearman
6  *
7  * Eric Kohl
8  * Aric Stewart
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #define NONAMELESSUNION
26 #define NONAMELESSSTRUCT
27 
28 #include "ws2tcpip.h"
29 
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35 
36 #include "windef.h"
37 #include "winbase.h"
38 #include "winuser.h"
39 #include "wininet.h"
40 #include "winineti.h"
41 #include "winerror.h"
42 #include "winreg.h"
43 #include "shlwapi.h"
44 #include "shlobj.h"
45 #include "shellapi.h"
46 
47 #include "internet.h"
48 
49 #include "wine/unicode.h"
50 #include "wine/debug.h"
51 
52 WINE_DEFAULT_DEBUG_CHANNEL(wininet);
53 
54 static const char urlcache_ver_prefix[] = "WINE URLCache Ver ";
55 static const char urlcache_ver[] = "0.2012001";
56 
57 #ifndef CHAR_BIT
58 #define CHAR_BIT    (8 * sizeof(CHAR))
59 #endif
60 
61 #define ENTRY_START_OFFSET      0x4000
62 #define DIR_LENGTH              8
63 #define MAX_DIR_NO              0x20
64 #define BLOCKSIZE               128
65 #define HASHTABLE_SIZE          448
66 #define HASHTABLE_NUM_ENTRIES   64 /* this needs to be power of 2, that divides HASHTABLE_SIZE */
67 #define HASHTABLE_BLOCKSIZE     (HASHTABLE_SIZE / HASHTABLE_NUM_ENTRIES)
68 #define ALLOCATION_TABLE_OFFSET 0x250
69 #define ALLOCATION_TABLE_SIZE   (ENTRY_START_OFFSET - ALLOCATION_TABLE_OFFSET)
70 #define MIN_BLOCK_NO            0x80
71 #define MAX_BLOCK_NO            (ALLOCATION_TABLE_SIZE * CHAR_BIT)
72 #define FILE_SIZE(blocks)       ((blocks) * BLOCKSIZE + ENTRY_START_OFFSET)
73 
74 #define HASHTABLE_URL           0
75 #define HASHTABLE_DEL           1
76 #define HASHTABLE_LOCK          2
77 #define HASHTABLE_FREE          3
78 #define HASHTABLE_REDR          5
79 #define HASHTABLE_FLAG_BITS     6
80 
81 #define PENDING_DELETE_CACHE_ENTRY  0x00400000
82 #define INSTALLED_CACHE_ENTRY       0x10000000
83 #define GET_INSTALLED_ENTRY         0x200
84 #define CACHE_CONTAINER_NO_SUBDIR   0xFE
85 
86 #define CACHE_HEADER_DATA_ROOT_LEAK_OFFSET 0x16
87 
88 #define FILETIME_SECOND 10000000
89 
90 #define DWORD_SIG(a,b,c,d)  (a | (b << 8) | (c << 16) | (d << 24))
91 #define URL_SIGNATURE   DWORD_SIG('U','R','L',' ')
92 #define REDR_SIGNATURE  DWORD_SIG('R','E','D','R')
93 #define LEAK_SIGNATURE  DWORD_SIG('L','E','A','K')
94 #define HASH_SIGNATURE  DWORD_SIG('H','A','S','H')
95 
96 #define DWORD_ALIGN(x) ( (DWORD)(((DWORD)(x)+sizeof(DWORD)-1)/sizeof(DWORD))*sizeof(DWORD) )
97 
98 #define URLCACHE_FIND_ENTRY_HANDLE_MAGIC 0xF389ABCD
99 
100 typedef struct
101 {
102     DWORD signature;
103     DWORD blocks_used; /* number of 128byte blocks used by this entry */
104 } entry_header;
105 
106 typedef struct
107 {
108     entry_header header;
109     FILETIME modification_time;
110     FILETIME access_time;
111     WORD expire_date; /* expire date in dos format */
112     WORD expire_time; /* expire time in dos format */
113     DWORD unk1; /* usually zero */
114     ULARGE_INTEGER size; /* see INTERNET_CACHE_ENTRY_INFO::dwSizeLow/High */
115     DWORD unk2; /* usually zero */
116     DWORD exempt_delta; /* see INTERNET_CACHE_ENTRY_INFO::dwExemptDelta */
117     DWORD unk3; /* usually 0x60 */
118     DWORD url_off; /* offset of start of url from start of entry */
119     BYTE cache_dir; /* index of cache directory this url is stored in */
120     BYTE unk4; /* usually zero */
121     WORD unk5; /* usually 0x1010 */
122     DWORD local_name_off; /* offset of start of local filename from start of entry */
123     DWORD cache_entry_type; /* see INTERNET_CACHE_ENTRY_INFO::CacheEntryType */
124     DWORD header_info_off; /* offset of start of header info from start of entry */
125     DWORD header_info_size;
126     DWORD file_extension_off; /* offset of start of file extension from start of entry */
127     WORD sync_date; /* last sync date in dos format */
128     WORD sync_time; /* last sync time in dos format */
129     DWORD hit_rate; /* see INTERNET_CACHE_ENTRY_INFO::dwHitRate */
130     DWORD use_count; /* see INTERNET_CACHE_ENTRY_INFO::dwUseCount */
131     WORD write_date;
132     WORD write_time;
133     DWORD unk7; /* usually zero */
134     DWORD unk8; /* usually zero */
135     /* packing to dword align start of next field */
136     /* CHAR szSourceUrlName[]; (url) */
137     /* packing to dword align start of next field */
138     /* CHAR szLocalFileName[]; (local file name excluding path) */
139     /* packing to dword align start of next field */
140     /* CHAR szHeaderInfo[]; (header info) */
141 } entry_url;
142 
143 struct hash_entry
144 {
145     DWORD key;
146     DWORD offset;
147 };
148 
149 typedef struct
150 {
151     entry_header header;
152     DWORD next;
153     DWORD id;
154     struct hash_entry hash_table[HASHTABLE_SIZE];
155 } entry_hash_table;
156 
157 typedef struct
158 {
159     char signature[28];
160     DWORD size;
161     DWORD hash_table_off;
162     DWORD capacity_in_blocks;
163     DWORD blocks_in_use;
164     DWORD unk1;
165     ULARGE_INTEGER cache_limit;
166     ULARGE_INTEGER cache_usage;
167     ULARGE_INTEGER exempt_usage;
168     DWORD dirs_no;
169     struct _directory_data
170     {
171         DWORD files_no;
172         char name[DIR_LENGTH];
173     } directory_data[MAX_DIR_NO];
174     DWORD options[0x21];
175     BYTE allocation_table[ALLOCATION_TABLE_SIZE];
176 } urlcache_header;
177 
178 typedef struct
179 {
180     HANDLE file;
181     CHAR url[1];
182 } stream_handle;
183 
184 typedef struct
185 {
186     struct list entry; /* part of a list */
187     char *cache_prefix; /* string that has to be prefixed for this container to be used */
188     LPWSTR path; /* path to url container directory */
189     HANDLE mapping; /* handle of file mapping */
190     DWORD file_size; /* size of file when mapping was opened */
191     HANDLE mutex; /* handle of mutex */
192     DWORD default_entry_type;
193 } cache_container;
194 
195 typedef struct
196 {
197     DWORD magic;
198     char *url_search_pattern;
199     DWORD container_idx;
200     DWORD hash_table_idx;
201     DWORD hash_entry_idx;
202 } find_handle;
203 
204 /* List of all containers available */
205 static struct list UrlContainers = LIST_INIT(UrlContainers);
206 /* ReactOS r54992 */
207 BOOL bDefaultContainersAdded = FALSE;
208 
209 static inline char *heap_strdupWtoUTF8(LPCWSTR str)
210 {
211     char *ret = NULL;
212 
213     if(str) {
214         DWORD size = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
215         ret = heap_alloc(size);
216         if(ret)
217             WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
218     }
219 
220     return ret;
221 }
222 
223 /***********************************************************************
224  *           urlcache_block_is_free (Internal)
225  *
226  *  Is the specified block number free?
227  *
228  * RETURNS
229  *    zero if free
230  *    non-zero otherwise
231  *
232  */
233 static inline BYTE urlcache_block_is_free(BYTE *allocation_table, DWORD block_number)
234 {
235     BYTE mask = 1 << (block_number%CHAR_BIT);
236     return (allocation_table[block_number/CHAR_BIT] & mask) == 0;
237 }
238 
239 /***********************************************************************
240  *           urlcache_block_free (Internal)
241  *
242  *  Marks the specified block as free
243  *
244  * CAUTION
245  *    this function is not updating used blocks count
246  *
247  * RETURNS
248  *    nothing
249  *
250  */
251 static inline void urlcache_block_free(BYTE *allocation_table, DWORD block_number)
252 {
253     BYTE mask = ~(1 << (block_number%CHAR_BIT));
254     allocation_table[block_number/CHAR_BIT] &= mask;
255 }
256 
257 /***********************************************************************
258  *           urlcache_block_alloc (Internal)
259  *
260  *  Marks the specified block as allocated
261  *
262  * CAUTION
263  *     this function is not updating used blocks count
264  *
265  * RETURNS
266  *    nothing
267  *
268  */
269 static inline void urlcache_block_alloc(BYTE *allocation_table, DWORD block_number)
270 {
271     BYTE mask = 1 << (block_number%CHAR_BIT);
272     allocation_table[block_number/CHAR_BIT] |= mask;
273 }
274 
275 /***********************************************************************
276  *           urlcache_entry_alloc (Internal)
277  *
278  *  Finds and allocates the first block of free space big enough and
279  * sets entry to point to it.
280  *
281  * RETURNS
282  *    ERROR_SUCCESS when free memory block was found
283  *    Any other Win32 error code if the entry could not be added
284  *
285  */
286 static DWORD urlcache_entry_alloc(urlcache_header *header, DWORD blocks_needed, entry_header **entry)
287 {
288     DWORD block, block_size;
289 
290     for(block=0; block<header->capacity_in_blocks; block+=block_size+1)
291     {
292         block_size = 0;
293         while(block_size<blocks_needed && block_size+block<header->capacity_in_blocks
294                 && urlcache_block_is_free(header->allocation_table, block+block_size))
295             block_size++;
296 
297         if(block_size == blocks_needed)
298         {
299             DWORD index;
300 
301             TRACE("Found free blocks starting at no. %d (0x%x)\n", block, ENTRY_START_OFFSET+block*BLOCKSIZE);
302 
303             for(index=0; index<blocks_needed; index++)
304                 urlcache_block_alloc(header->allocation_table, block+index);
305 
306             *entry = (entry_header*)((BYTE*)header+ENTRY_START_OFFSET+block*BLOCKSIZE);
307             for(index=0; index<blocks_needed*BLOCKSIZE/sizeof(DWORD); index++)
308                 ((DWORD*)*entry)[index] = 0xdeadbeef;
309             (*entry)->blocks_used = blocks_needed;
310 
311             header->blocks_in_use += blocks_needed;
312             return ERROR_SUCCESS;
313         }
314     }
315 
316     return ERROR_HANDLE_DISK_FULL;
317 }
318 
319 /***********************************************************************
320  *           urlcache_entry_free (Internal)
321  *
322  *  Deletes the specified entry and frees the space allocated to it
323  *
324  * RETURNS
325  *    TRUE if it succeeded
326  *    FALSE if it failed
327  *
328  */
329 static BOOL urlcache_entry_free(urlcache_header *header, entry_header *entry)
330 {
331     DWORD start_block, block;
332 
333     /* update allocation table */
334     start_block = ((DWORD)((BYTE*)entry - (BYTE*)header) - ENTRY_START_OFFSET) / BLOCKSIZE;
335     for(block = start_block; block < start_block+entry->blocks_used; block++)
336         urlcache_block_free(header->allocation_table, block);
337 
338     header->blocks_in_use -= entry->blocks_used;
339     return TRUE;
340 }
341 
342 /***********************************************************************
343  *           urlcache_create_hash_table (Internal)
344  *
345  *  Creates a new hash table in free space and adds it to the chain of existing
346  * hash tables.
347  *
348  * RETURNS
349  *    ERROR_SUCCESS if the hash table was created
350  *    ERROR_DISK_FULL if the hash table could not be created
351  *
352  */
353 static DWORD urlcache_create_hash_table(urlcache_header *header, entry_hash_table *hash_table_prev, entry_hash_table **hash_table)
354 {
355     DWORD dwOffset, error;
356     int i;
357 
358     if((error = urlcache_entry_alloc(header, 0x20, (entry_header**)hash_table)) != ERROR_SUCCESS)
359         return error;
360 
361     dwOffset = (BYTE*)*hash_table-(BYTE*)header;
362 
363     if(hash_table_prev)
364         hash_table_prev->next = dwOffset;
365     else
366         header->hash_table_off = dwOffset;
367 
368     (*hash_table)->header.signature = HASH_SIGNATURE;
369     (*hash_table)->next = 0;
370     (*hash_table)->id = hash_table_prev ? hash_table_prev->id+1 : 0;
371     for(i = 0; i < HASHTABLE_SIZE; i++) {
372         (*hash_table)->hash_table[i].offset = HASHTABLE_FREE;
373         (*hash_table)->hash_table[i].key = HASHTABLE_FREE;
374     }
375     return ERROR_SUCCESS;
376 }
377 
378 /***********************************************************************
379  *           cache_container_create_object_name (Internal)
380  *
381  *  Converts a path to a name suitable for use as a Win32 object name.
382  * Replaces '\\' characters in-place with the specified character
383  * (usually '_' or '!')
384  *
385  * RETURNS
386  *    nothing
387  *
388  */
389 static void cache_container_create_object_name(LPWSTR lpszPath, WCHAR replace)
390 {
391     for (; *lpszPath; lpszPath++)
392     {
393         if (*lpszPath == '\\')
394             *lpszPath = replace;
395     }
396 }
397 
398 /* Caller must hold container lock */
399 static HANDLE cache_container_map_index(HANDLE file, const WCHAR *path, DWORD size, BOOL *validate)
400 {
401     static const WCHAR mapping_name_format[]
402         = {'%','s','i','n','d','e','x','.','d','a','t','_','%','l','u',0};
403     WCHAR mapping_name[MAX_PATH];
404     HANDLE mapping;
405 
406     wsprintfW(mapping_name, mapping_name_format, path, size);
407     cache_container_create_object_name(mapping_name, '_');
408 
409     mapping = OpenFileMappingW(FILE_MAP_WRITE, FALSE, mapping_name);
410     if(mapping) {
411         if(validate) *validate = FALSE;
412         return mapping;
413     }
414 
415     if(validate) *validate = TRUE;
416     return CreateFileMappingW(file, NULL, PAGE_READWRITE, 0, 0, mapping_name);
417 }
418 
419 /* Caller must hold container lock */
420 static DWORD cache_container_set_size(cache_container *container, HANDLE file, DWORD blocks_no)
421 {
422     static const WCHAR cache_content_key[] = {'S','o','f','t','w','a','r','e','\\',
423         'M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\',
424         'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
425         'I','n','t','e','r','n','e','t',' ','S','e','t','t','i','n','g','s','\\',
426         'C','a','c','h','e','\\','C','o','n','t','e','n','t',0};
427     static const WCHAR cache_limit[] = {'C','a','c','h','e','L','i','m','i','t',0};
428 
429     DWORD file_size = FILE_SIZE(blocks_no);
430     WCHAR dir_path[MAX_PATH], *dir_name;
431     entry_hash_table *hashtable_entry;
432     urlcache_header *header;
433     HANDLE mapping;
434     FILETIME ft;
435     HKEY key;
436     int i, j;
437 
438     if(SetFilePointer(file, file_size, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
439         return GetLastError();
440 
441     if(!SetEndOfFile(file))
442         return GetLastError();
443 
444     mapping = cache_container_map_index(file, container->path, file_size, NULL);
445     if(!mapping)
446         return GetLastError();
447 
448     header = MapViewOfFile(mapping, FILE_MAP_WRITE, 0, 0, 0);
449     if(!header) {
450         CloseHandle(mapping);
451         return GetLastError();
452     }
453 
454     if(blocks_no != MIN_BLOCK_NO) {
455         if(file_size > header->size)
456             memset((char*)header+header->size, 0, file_size-header->size);
457         header->size = file_size;
458         header->capacity_in_blocks = blocks_no;
459 
460         UnmapViewOfFile(header);
461         CloseHandle(container->mapping);
462         container->mapping = mapping;
463         container->file_size = file_size;
464         return ERROR_SUCCESS;
465     }
466 
467     memset(header, 0, file_size);
468     /* First set some constants and defaults in the header */
469     memcpy(header->signature, urlcache_ver_prefix, sizeof(urlcache_ver_prefix)-1);
470     memcpy(header->signature+sizeof(urlcache_ver_prefix)-1, urlcache_ver, sizeof(urlcache_ver)-1);
471     header->size = file_size;
472     header->capacity_in_blocks = blocks_no;
473     /* 127MB - taken from default for Windows 2000 */
474     header->cache_limit.QuadPart = 0x07ff5400;
475     /* Copied from a Windows 2000 cache index */
476     header->dirs_no = container->default_entry_type==NORMAL_CACHE_ENTRY ? 4 : 0;
477 
478     /* If the registry has a cache size set, use the registry value */
479     if(RegOpenKeyW(HKEY_CURRENT_USER, cache_content_key, &key) == ERROR_SUCCESS) {
480         DWORD dw, len = sizeof(dw), keytype;
481 
482         if(RegQueryValueExW(key, cache_limit, NULL, &keytype, (BYTE*)&dw, &len) == ERROR_SUCCESS &&
483                 keytype == REG_DWORD)
484             header->cache_limit.QuadPart = (ULONGLONG)dw * 1024;
485         RegCloseKey(key);
486     }
487 
488     urlcache_create_hash_table(header, NULL, &hashtable_entry);
489 
490     /* Last step - create the directories */
491     strcpyW(dir_path, container->path);
492     dir_name = dir_path + strlenW(dir_path);
493     dir_name[8] = 0;
494 
495     GetSystemTimeAsFileTime(&ft);
496 
497     for(i=0; i<header->dirs_no; ++i) {
498         header->directory_data[i].files_no = 0;
499         for(j=0;; ++j) {
500             ULONGLONG n = ft.dwHighDateTime;
501             int k;
502 
503             /* Generate a file name to attempt to create.
504              * This algorithm will create what will appear
505              * to be random and unrelated directory names
506              * of up to 9 characters in length.
507              */
508             n <<= 32;
509             n += ft.dwLowDateTime;
510             n ^= ((ULONGLONG) i << 56) | ((ULONGLONG) j << 48);
511 
512             for(k = 0; k < 8; ++k) {
513                 int r = (n % 36);
514 
515                 /* Dividing by a prime greater than 36 helps
516                  * with the appearance of randomness
517                  */
518                 n /= 37;
519 
520                 if(r < 10)
521                     dir_name[k] = '0' + r;
522                 else
523                     dir_name[k] = 'A' + (r - 10);
524             }
525 
526             if(CreateDirectoryW(dir_path, 0)) {
527                 /* The following is OK because we generated an
528                  * 8 character directory name made from characters
529                  * [A-Z0-9], which are equivalent for all code
530                  * pages and for UTF-16
531                  */
532                 for (k = 0; k < 8; ++k)
533                     header->directory_data[i].name[k] = dir_name[k];
534                 break;
535             }else if(j >= 255) {
536                 /* Give up. The most likely cause of this
537                  * is a full disk, but whatever the cause
538                  * is, it should be more than apparent that
539                  * we won't succeed.
540                  */
541                 UnmapViewOfFile(header);
542                 CloseHandle(mapping);
543                 return GetLastError();
544             }
545         }
546     }
547 
548     UnmapViewOfFile(header);
549     CloseHandle(container->mapping);
550     container->mapping = mapping;
551     container->file_size = file_size;
552     return ERROR_SUCCESS;
553 }
554 
555 static BOOL cache_container_is_valid(urlcache_header *header, DWORD file_size)
556 {
557     DWORD allocation_size, count_bits, i;
558 
559     if(file_size < FILE_SIZE(MIN_BLOCK_NO))
560         return FALSE;
561 
562     if(file_size != header->size)
563         return FALSE;
564 
565     if (!memcmp(header->signature, urlcache_ver_prefix, sizeof(urlcache_ver_prefix)-1) &&
566             memcmp(header->signature+sizeof(urlcache_ver_prefix)-1, urlcache_ver, sizeof(urlcache_ver)-1))
567         return FALSE;
568 
569     if(FILE_SIZE(header->capacity_in_blocks) != file_size)
570         return FALSE;
571 
572     allocation_size = 0;
573     for(i=0; i<header->capacity_in_blocks/8; i++) {
574         for(count_bits = header->allocation_table[i]; count_bits!=0; count_bits>>=1) {
575             if(count_bits & 1)
576                 allocation_size++;
577         }
578     }
579     if(allocation_size != header->blocks_in_use)
580         return FALSE;
581 
582     for(; i<ALLOCATION_TABLE_SIZE; i++) {
583         if(header->allocation_table[i])
584             return FALSE;
585     }
586 
587     return TRUE;
588 }
589 
590 /***********************************************************************
591  *           cache_container_open_index (Internal)
592  *
593  *  Opens the index file and saves mapping handle
594  *
595  * RETURNS
596  *    ERROR_SUCCESS if succeeded
597  *    Any other Win32 error code if failed
598  *
599  */
600 static DWORD cache_container_open_index(cache_container *container, DWORD blocks_no)
601 {
602     static const WCHAR index_dat[] = {'i','n','d','e','x','.','d','a','t',0};
603 
604     HANDLE file;
605     WCHAR index_path[MAX_PATH];
606     DWORD file_size;
607     BOOL validate;
608 
609     WaitForSingleObject(container->mutex, INFINITE);
610 
611     if(container->mapping) {
612         ReleaseMutex(container->mutex);
613         return ERROR_SUCCESS;
614     }
615 
616     strcpyW(index_path, container->path);
617     strcatW(index_path, index_dat);
618 
619     file = CreateFileW(index_path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
620     if(file == INVALID_HANDLE_VALUE) {
621 	/* Maybe the directory wasn't there? Try to create it */
622 	if(CreateDirectoryW(container->path, 0))
623             file = CreateFileW(index_path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
624     }
625     if(file == INVALID_HANDLE_VALUE) {
626         TRACE("Could not open or create cache index file \"%s\"\n", debugstr_w(index_path));
627         ReleaseMutex(container->mutex);
628         return GetLastError();
629     }
630 
631     file_size = GetFileSize(file, NULL);
632     if(file_size == INVALID_FILE_SIZE) {
633         CloseHandle(file);
634 	ReleaseMutex(container->mutex);
635         return GetLastError();
636     }
637 
638     if(blocks_no < MIN_BLOCK_NO)
639         blocks_no = MIN_BLOCK_NO;
640     else if(blocks_no > MAX_BLOCK_NO)
641         blocks_no = MAX_BLOCK_NO;
642 
643     if(file_size < FILE_SIZE(blocks_no)) {
644         DWORD ret = cache_container_set_size(container, file, blocks_no);
645         CloseHandle(file);
646         ReleaseMutex(container->mutex);
647         return ret;
648     }
649 
650     container->file_size = file_size;
651     container->mapping = cache_container_map_index(file, container->path, file_size, &validate);
652     CloseHandle(file);
653     if(container->mapping && validate) {
654         urlcache_header *header = MapViewOfFile(container->mapping, FILE_MAP_WRITE, 0, 0, 0);
655 
656         if(header && !cache_container_is_valid(header, file_size)) {
657             WARN("detected old or broken index.dat file\n");
658             UnmapViewOfFile(header);
659             FreeUrlCacheSpaceW(container->path, 100, 0);
660         }else if(header) {
661             UnmapViewOfFile(header);
662         }else {
663             CloseHandle(container->mapping);
664             container->mapping = NULL;
665         }
666     }
667 
668     if(!container->mapping)
669     {
670         ERR("Couldn't create file mapping (error is %d)\n", GetLastError());
671         ReleaseMutex(container->mutex);
672         return GetLastError();
673     }
674 
675     ReleaseMutex(container->mutex);
676     return ERROR_SUCCESS;
677 }
678 
679 /***********************************************************************
680  *           cache_container_close_index (Internal)
681  *
682  *  Closes the index
683  *
684  * RETURNS
685  *    nothing
686  *
687  */
688 static void cache_container_close_index(cache_container *pContainer)
689 {
690     CloseHandle(pContainer->mapping);
691     pContainer->mapping = NULL;
692 }
693 
694 static BOOL cache_containers_add(const char *cache_prefix, LPCWSTR path,
695         DWORD default_entry_type, LPWSTR mutex_name)
696 {
697     cache_container *pContainer = heap_alloc(sizeof(cache_container));
698     int cache_prefix_len = strlen(cache_prefix);
699 
700     if (!pContainer)
701     {
702         return FALSE;
703     }
704 
705     pContainer->mapping = NULL;
706     pContainer->file_size = 0;
707     pContainer->default_entry_type = default_entry_type;
708 
709     pContainer->path = heap_strdupW(path);
710     if (!pContainer->path)
711     {
712         heap_free(pContainer);
713         return FALSE;
714     }
715 
716     pContainer->cache_prefix = heap_alloc(cache_prefix_len+1);
717     if (!pContainer->cache_prefix)
718     {
719         heap_free(pContainer->path);
720         heap_free(pContainer);
721         return FALSE;
722     }
723 
724     memcpy(pContainer->cache_prefix, cache_prefix, cache_prefix_len+1);
725 
726     CharLowerW(mutex_name);
727     cache_container_create_object_name(mutex_name, '!');
728 
729     if ((pContainer->mutex = CreateMutexW(NULL, FALSE, mutex_name)) == NULL)
730     {
731         ERR("couldn't create mutex (error is %d)\n", GetLastError());
732         heap_free(pContainer->path);
733         heap_free(pContainer);
734         return FALSE;
735     }
736 
737     list_add_head(&UrlContainers, &pContainer->entry);
738 
739     return TRUE;
740 }
741 
742 static void cache_container_delete_container(cache_container *pContainer)
743 {
744     list_remove(&pContainer->entry);
745 
746     cache_container_close_index(pContainer);
747     CloseHandle(pContainer->mutex);
748     heap_free(pContainer->path);
749     heap_free(pContainer->cache_prefix);
750     heap_free(pContainer);
751 }
752 
753 static void cache_containers_init(void)
754 {
755     static const WCHAR UrlSuffix[] = {'C','o','n','t','e','n','t','.','I','E','5',0};
756     static const WCHAR HistorySuffix[] = {'H','i','s','t','o','r','y','.','I','E','5',0};
757     static const WCHAR CookieSuffix[] = {0};
758     /* ReactOS r50916 */
759     static const WCHAR UserProfile[] = {'U','S','E','R','P','R','O','F','I','L','E',0};
760     static const struct
761     {
762         int nFolder; /* CSIDL_* constant */
763         const WCHAR *shpath_suffix; /* suffix on path returned by SHGetSpecialFolderPath */
764         const char *cache_prefix; /* prefix used to reference the container */
765         DWORD default_entry_type;
766     } DefaultContainerData[] =
767     {
768         { CSIDL_INTERNET_CACHE, UrlSuffix, "", NORMAL_CACHE_ENTRY },
769         { CSIDL_HISTORY, HistorySuffix, "Visited:", URLHISTORY_CACHE_ENTRY },
770         { CSIDL_COOKIES, CookieSuffix, "Cookie:", COOKIE_CACHE_ENTRY },
771     };
772     DWORD i;
773 
774     /* ReactOS r50916 */
775     if (GetEnvironmentVariableW(UserProfile, NULL, 0) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND)
776     {
777         ERR("Environment variable 'USERPROFILE' does not exist!\n");
778         return;
779     }
780 
781     for (i = 0; i < ARRAY_SIZE(DefaultContainerData); i++)
782     {
783         WCHAR wszCachePath[MAX_PATH];
784         WCHAR wszMutexName[MAX_PATH];
785         int path_len, suffix_len;
786         BOOL def_char;
787 
788         if (!SHGetSpecialFolderPathW(NULL, wszCachePath, DefaultContainerData[i].nFolder, TRUE))
789         {
790             ERR("Couldn't get path for default container %u\n", i);
791             continue;
792         }
793         path_len = strlenW(wszCachePath);
794         suffix_len = strlenW(DefaultContainerData[i].shpath_suffix);
795 
796         if (path_len + suffix_len + 2 > MAX_PATH)
797         {
798             ERR("Path too long\n");
799             continue;
800         }
801 
802         wszCachePath[path_len] = '\\';
803         wszCachePath[path_len+1] = 0;
804 
805         strcpyW(wszMutexName, wszCachePath);
806 
807         if (suffix_len)
808         {
809             memcpy(wszCachePath + path_len + 1, DefaultContainerData[i].shpath_suffix, (suffix_len + 1) * sizeof(WCHAR));
810             wszCachePath[path_len + suffix_len + 1] = '\\';
811             wszCachePath[path_len + suffix_len + 2] = '\0';
812         }
813 
814         if (!WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wszCachePath, path_len,
815                     NULL, 0, NULL, &def_char) || def_char)
816         {
817             WCHAR tmp[MAX_PATH];
818 
819             /* cannot convert path to ANSI code page */
820             if (!(path_len = GetShortPathNameW(wszCachePath, tmp, MAX_PATH)) ||
821                 !WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, tmp, path_len,
822                     NULL, 0, NULL, &def_char) || def_char)
823                 ERR("Can't create container path accessible by ANSI functions\n");
824             else
825                 memcpy(wszCachePath, tmp, (path_len+1)*sizeof(WCHAR));
826         }
827 
828         cache_containers_add(DefaultContainerData[i].cache_prefix, wszCachePath,
829                 DefaultContainerData[i].default_entry_type, wszMutexName);
830     }
831 
832 #ifdef __REACTOS__
833     bDefaultContainersAdded = TRUE;
834 #endif
835 }
836 
837 static void cache_containers_free(void)
838 {
839     while(!list_empty(&UrlContainers))
840         cache_container_delete_container(
841             LIST_ENTRY(list_head(&UrlContainers), cache_container, entry)
842         );
843 }
844 
845 static DWORD cache_containers_find(const char *url, cache_container **ret)
846 {
847     cache_container *container;
848 
849     TRACE("searching for prefix for URL: %s\n", debugstr_a(url));
850 
851     if(!url)
852         return ERROR_INVALID_PARAMETER;
853 
854 #ifdef __REACTOS__
855     /* ReactOS r54992 */
856     if (!bDefaultContainersAdded)
857         cache_containers_init();
858 #endif
859 
860     LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
861     {
862         int prefix_len = strlen(container->cache_prefix);
863 
864         if(!strncmp(container->cache_prefix, url, prefix_len)) {
865             TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
866             *ret = container;
867             return ERROR_SUCCESS;
868         }
869     }
870 
871     ERR("no container found\n");
872     return ERROR_FILE_NOT_FOUND;
873 }
874 
875 static BOOL cache_containers_enum(char *search_pattern, DWORD index, cache_container **ret)
876 {
877     DWORD i = 0;
878     cache_container *container;
879 
880     TRACE("searching for prefix: %s\n", debugstr_a(search_pattern));
881 
882     /* non-NULL search pattern only returns one container ever */
883     if (search_pattern && index > 0)
884         return FALSE;
885 
886 #ifdef __REACTOS__
887     /* ReactOS r54992 */
888     if (!bDefaultContainersAdded)
889         cache_containers_init();
890 #endif
891 
892     LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
893     {
894         if (search_pattern)
895         {
896             if (!strcmp(container->cache_prefix, search_pattern))
897             {
898                 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
899                 *ret = container;
900                 return TRUE;
901             }
902         }
903         else
904         {
905             if (i == index)
906             {
907                 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
908                 *ret = container;
909                 return TRUE;
910             }
911         }
912         i++;
913     }
914     return FALSE;
915 }
916 
917 /***********************************************************************
918  *           cache_container_lock_index (Internal)
919  *
920  * Locks the index for system-wide exclusive access.
921  *
922  * RETURNS
923  *  Cache file header if successful
924  *  NULL if failed and calls SetLastError.
925  */
926 static urlcache_header* cache_container_lock_index(cache_container *pContainer)
927 {
928     BYTE index;
929     LPVOID pIndexData;
930     urlcache_header* pHeader;
931     DWORD error;
932 
933     /* acquire mutex */
934     WaitForSingleObject(pContainer->mutex, INFINITE);
935 
936     pIndexData = MapViewOfFile(pContainer->mapping, FILE_MAP_WRITE, 0, 0, 0);
937 
938     if (!pIndexData)
939     {
940         ReleaseMutex(pContainer->mutex);
941         ERR("Couldn't MapViewOfFile. Error: %d\n", GetLastError());
942         return NULL;
943     }
944     pHeader = (urlcache_header*)pIndexData;
945 
946     /* file has grown - we need to remap to prevent us getting
947      * access violations when we try and access beyond the end
948      * of the memory mapped file */
949     if (pHeader->size != pContainer->file_size)
950     {
951         UnmapViewOfFile( pHeader );
952         cache_container_close_index(pContainer);
953         error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
954         if (error != ERROR_SUCCESS)
955         {
956             ReleaseMutex(pContainer->mutex);
957             SetLastError(error);
958             return NULL;
959         }
960         pIndexData = MapViewOfFile(pContainer->mapping, FILE_MAP_WRITE, 0, 0, 0);
961 
962         if (!pIndexData)
963         {
964             ReleaseMutex(pContainer->mutex);
965             ERR("Couldn't MapViewOfFile. Error: %d\n", GetLastError());
966             return NULL;
967         }
968         pHeader = (urlcache_header*)pIndexData;
969     }
970 
971     TRACE("Signature: %s, file size: %d bytes\n", pHeader->signature, pHeader->size);
972 
973     for (index = 0; index < pHeader->dirs_no; index++)
974     {
975         TRACE("Directory[%d] = \"%.8s\"\n", index, pHeader->directory_data[index].name);
976     }
977 
978     return pHeader;
979 }
980 
981 /***********************************************************************
982  *           cache_container_unlock_index (Internal)
983  *
984  */
985 static BOOL cache_container_unlock_index(cache_container *pContainer, urlcache_header *pHeader)
986 {
987     /* release mutex */
988     ReleaseMutex(pContainer->mutex);
989     return UnmapViewOfFile(pHeader);
990 }
991 
992 /***********************************************************************
993  *           urlcache_create_file_pathW (Internal)
994  *
995  *  Copies the full path to the specified buffer given the local file
996  * name and the index of the directory it is in. Always sets value in
997  * lpBufferSize to the required buffer size (in bytes).
998  *
999  * RETURNS
1000  *    TRUE if the buffer was big enough
1001  *    FALSE if the buffer was too small
1002  *
1003  */
1004 static BOOL urlcache_create_file_pathW(
1005     const cache_container *pContainer,
1006     const urlcache_header *pHeader,
1007     LPCSTR szLocalFileName,
1008     BYTE Directory,
1009     LPWSTR wszPath,
1010     LPLONG lpBufferSize,
1011     BOOL trunc_name)
1012 {
1013     LONG nRequired;
1014     int path_len = strlenW(pContainer->path);
1015     int file_name_len = MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, NULL, 0);
1016     if (Directory!=CACHE_CONTAINER_NO_SUBDIR && Directory>=pHeader->dirs_no)
1017     {
1018         *lpBufferSize = 0;
1019         return FALSE;
1020     }
1021 
1022     nRequired = (path_len + file_name_len) * sizeof(WCHAR);
1023     if(Directory != CACHE_CONTAINER_NO_SUBDIR)
1024         nRequired += (DIR_LENGTH + 1) * sizeof(WCHAR);
1025     if (trunc_name && nRequired >= *lpBufferSize)
1026         nRequired = *lpBufferSize;
1027     if (nRequired <= *lpBufferSize)
1028     {
1029         int dir_len;
1030 
1031         memcpy(wszPath, pContainer->path, path_len * sizeof(WCHAR));
1032         if (Directory != CACHE_CONTAINER_NO_SUBDIR)
1033         {
1034             dir_len = MultiByteToWideChar(CP_ACP, 0, pHeader->directory_data[Directory].name, DIR_LENGTH, wszPath + path_len, DIR_LENGTH);
1035             wszPath[dir_len + path_len] = '\\';
1036             dir_len++;
1037         }
1038         else
1039         {
1040             dir_len = 0;
1041         }
1042         MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, wszPath + dir_len + path_len,
1043                 *lpBufferSize/sizeof(WCHAR)-dir_len-path_len);
1044         wszPath[*lpBufferSize/sizeof(WCHAR)-1] = 0;
1045         *lpBufferSize = nRequired;
1046         return TRUE;
1047     }
1048     *lpBufferSize = nRequired;
1049     return FALSE;
1050 }
1051 
1052 /***********************************************************************
1053  *           urlcache_create_file_pathA (Internal)
1054  *
1055  *  Copies the full path to the specified buffer given the local file
1056  * name and the index of the directory it is in. Always sets value in
1057  * lpBufferSize to the required buffer size.
1058  *
1059  * RETURNS
1060  *    TRUE if the buffer was big enough
1061  *    FALSE if the buffer was too small
1062  *
1063  */
1064 static BOOL urlcache_create_file_pathA(
1065     const cache_container *pContainer,
1066     const urlcache_header *pHeader,
1067     LPCSTR szLocalFileName,
1068     BYTE Directory,
1069     LPSTR szPath,
1070     LPLONG lpBufferSize)
1071 {
1072     LONG nRequired;
1073     int path_len, file_name_len, dir_len;
1074 
1075     if (Directory!=CACHE_CONTAINER_NO_SUBDIR && Directory>=pHeader->dirs_no)
1076     {
1077         *lpBufferSize = 0;
1078         return FALSE;
1079     }
1080 
1081     path_len = WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, NULL, 0, NULL, NULL) - 1;
1082     file_name_len = strlen(szLocalFileName) + 1 /* for nul-terminator */;
1083     if (Directory!=CACHE_CONTAINER_NO_SUBDIR)
1084         dir_len = DIR_LENGTH+1;
1085     else
1086         dir_len = 0;
1087 
1088     nRequired = (path_len + dir_len + file_name_len) * sizeof(char);
1089     if (nRequired <= *lpBufferSize)
1090     {
1091         WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, szPath, path_len, NULL, NULL);
1092         if(dir_len) {
1093             memcpy(szPath+path_len, pHeader->directory_data[Directory].name, dir_len-1);
1094             szPath[path_len + dir_len-1] = '\\';
1095         }
1096         memcpy(szPath + path_len + dir_len, szLocalFileName, file_name_len);
1097         *lpBufferSize = nRequired;
1098         return TRUE;
1099     }
1100     *lpBufferSize = nRequired;
1101     return FALSE;
1102 }
1103 
1104 /* Just like FileTimeToDosDateTime, except that it also maps the special
1105  * case of a filetime of (0,0) to a DOS date/time of (0,0).
1106  */
1107 static void file_time_to_dos_date_time(const FILETIME *ft, WORD *fatdate,
1108                                            WORD *fattime)
1109 {
1110     if (!ft->dwLowDateTime && !ft->dwHighDateTime)
1111         *fatdate = *fattime = 0;
1112     else
1113         FileTimeToDosDateTime(ft, fatdate, fattime);
1114 }
1115 
1116 /***********************************************************************
1117  *           urlcache_delete_file (Internal)
1118  */
1119 static DWORD urlcache_delete_file(const cache_container *container,
1120         urlcache_header *header, entry_url *url_entry)
1121 {
1122     WIN32_FILE_ATTRIBUTE_DATA attr;
1123     WCHAR path[MAX_PATH];
1124     LONG path_size = sizeof(path);
1125     DWORD err;
1126     WORD date, time;
1127 
1128     if(!url_entry->local_name_off)
1129         goto succ;
1130 
1131     if(!urlcache_create_file_pathW(container, header,
1132                 (LPCSTR)url_entry+url_entry->local_name_off,
1133                 url_entry->cache_dir, path, &path_size, FALSE))
1134         goto succ;
1135 
1136     if(!GetFileAttributesExW(path, GetFileExInfoStandard, &attr))
1137         goto succ;
1138     file_time_to_dos_date_time(&attr.ftLastWriteTime, &date, &time);
1139     if(date != url_entry->write_date || time != url_entry->write_time)
1140         goto succ;
1141 
1142     err = (DeleteFileW(path) ? ERROR_SUCCESS : GetLastError());
1143     if(err == ERROR_ACCESS_DENIED || err == ERROR_SHARING_VIOLATION)
1144         return err;
1145 
1146 succ:
1147     if (url_entry->cache_dir < header->dirs_no)
1148     {
1149         if (header->directory_data[url_entry->cache_dir].files_no)
1150             header->directory_data[url_entry->cache_dir].files_no--;
1151     }
1152     if (url_entry->cache_entry_type & STICKY_CACHE_ENTRY)
1153     {
1154         if (url_entry->size.QuadPart < header->exempt_usage.QuadPart)
1155             header->exempt_usage.QuadPart -= url_entry->size.QuadPart;
1156         else
1157             header->exempt_usage.QuadPart = 0;
1158     }
1159     else
1160     {
1161         if (url_entry->size.QuadPart < header->cache_usage.QuadPart)
1162             header->cache_usage.QuadPart -= url_entry->size.QuadPart;
1163         else
1164             header->cache_usage.QuadPart = 0;
1165     }
1166 
1167     return ERROR_SUCCESS;
1168 }
1169 
1170 static BOOL urlcache_clean_leaked_entries(cache_container *container, urlcache_header *header)
1171 {
1172     DWORD *leak_off;
1173     BOOL freed = FALSE;
1174 
1175     leak_off = &header->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET];
1176     while(*leak_off) {
1177         entry_url *url_entry = (entry_url*)((LPBYTE)header + *leak_off);
1178 
1179         if(SUCCEEDED(urlcache_delete_file(container, header, url_entry))) {
1180             *leak_off = url_entry->exempt_delta;
1181             urlcache_entry_free(header, &url_entry->header);
1182             freed = TRUE;
1183         }else {
1184             leak_off = &url_entry->exempt_delta;
1185         }
1186     }
1187 
1188     return freed;
1189 }
1190 
1191 /***********************************************************************
1192  *           cache_container_clean_index (Internal)
1193  *
1194  * This function is meant to make place in index file by removing leaked
1195  * files entries and resizing the file.
1196  *
1197  * CAUTION: file view may get mapped to new memory
1198  *
1199  * RETURNS
1200  *     ERROR_SUCCESS when new memory is available
1201  *     error code otherwise
1202  */
1203 static DWORD cache_container_clean_index(cache_container *container, urlcache_header **file_view)
1204 {
1205     urlcache_header *header = *file_view;
1206     DWORD ret;
1207 
1208     TRACE("(%s %s)\n", debugstr_a(container->cache_prefix), debugstr_w(container->path));
1209 
1210     if(urlcache_clean_leaked_entries(container, header))
1211         return ERROR_SUCCESS;
1212 
1213     if(header->size >= ALLOCATION_TABLE_SIZE*8*BLOCKSIZE + ENTRY_START_OFFSET) {
1214         WARN("index file has maximal size\n");
1215         return ERROR_NOT_ENOUGH_MEMORY;
1216     }
1217 
1218     cache_container_close_index(container);
1219     ret = cache_container_open_index(container, header->capacity_in_blocks*2);
1220     if(ret != ERROR_SUCCESS)
1221         return ret;
1222     header = MapViewOfFile(container->mapping, FILE_MAP_WRITE, 0, 0, 0);
1223     if(!header)
1224         return GetLastError();
1225 
1226     UnmapViewOfFile(*file_view);
1227     *file_view = header;
1228     return ERROR_SUCCESS;
1229 }
1230 
1231 /* Just like DosDateTimeToFileTime, except that it also maps the special
1232  * case of a DOS date/time of (0,0) to a filetime of (0,0).
1233  */
1234 static void dos_date_time_to_file_time(WORD fatdate, WORD fattime,
1235                                            FILETIME *ft)
1236 {
1237     if (!fatdate && !fattime)
1238         ft->dwLowDateTime = ft->dwHighDateTime = 0;
1239     else
1240         DosDateTimeToFileTime(fatdate, fattime, ft);
1241 }
1242 
1243 static int urlcache_decode_url(const char *url, WCHAR *decoded_url, int decoded_len)
1244 {
1245     URL_COMPONENTSA uc;
1246     DWORD len, part_len;
1247     WCHAR *host_name;
1248 
1249     memset(&uc, 0, sizeof(uc));
1250     uc.dwStructSize = sizeof(uc);
1251     uc.dwHostNameLength = 1;
1252     if(!InternetCrackUrlA(url, 0, 0, &uc))
1253         uc.nScheme = INTERNET_SCHEME_UNKNOWN;
1254 
1255     if(uc.nScheme!=INTERNET_SCHEME_HTTP && uc.nScheme!=INTERNET_SCHEME_HTTPS)
1256         return MultiByteToWideChar(CP_UTF8, 0, url, -1, decoded_url, decoded_len);
1257 
1258     if(!decoded_url)
1259         decoded_len = 0;
1260 
1261     len = MultiByteToWideChar(CP_UTF8, 0, url, uc.lpszHostName-url, decoded_url, decoded_len);
1262     if(!len)
1263         return 0;
1264     if(decoded_url)
1265         decoded_len -= len;
1266 
1267     host_name = heap_alloc(uc.dwHostNameLength*sizeof(WCHAR));
1268     if(!host_name)
1269         return 0;
1270     if(!MultiByteToWideChar(CP_UTF8, 0, uc.lpszHostName, uc.dwHostNameLength,
1271                 host_name, uc.dwHostNameLength)) {
1272         heap_free(host_name);
1273         return 0;
1274     }
1275     part_len = IdnToUnicode(0, host_name, uc.dwHostNameLength,
1276             decoded_url ? decoded_url+len : NULL, decoded_len);
1277     heap_free(host_name);
1278     if(!part_len) {
1279         SetLastError(ERROR_INTERNET_INVALID_URL);
1280         return 0;
1281     }
1282     len += part_len;
1283     if(decoded_url)
1284         decoded_len -= part_len;
1285 
1286     part_len = MultiByteToWideChar(CP_UTF8, 0,
1287             uc.lpszHostName+uc.dwHostNameLength,
1288             -1, decoded_url ? decoded_url+len : NULL, decoded_len);
1289     if(!part_len)
1290         return 0;
1291     len += part_len;
1292 
1293     return len;
1294 }
1295 
1296 /***********************************************************************
1297  *           urlcache_copy_entry (Internal)
1298  *
1299  *  Copies an entry from the cache index file to the Win32 structure
1300  *
1301  * RETURNS
1302  *    ERROR_SUCCESS if the buffer was big enough
1303  *    ERROR_INSUFFICIENT_BUFFER if the buffer was too small
1304  *
1305  */
1306 static DWORD urlcache_copy_entry(cache_container *container, const urlcache_header *header,
1307         INTERNET_CACHE_ENTRY_INFOA *entry_info, DWORD *info_size, const entry_url *url_entry, BOOL unicode)
1308 {
1309     int url_len;
1310     DWORD size = sizeof(*entry_info);
1311 
1312     if(*info_size >= size) {
1313         entry_info->lpHeaderInfo = NULL;
1314         entry_info->lpszFileExtension = NULL;
1315         entry_info->lpszLocalFileName = NULL;
1316         entry_info->lpszSourceUrlName = NULL;
1317         entry_info->CacheEntryType = url_entry->cache_entry_type;
1318         entry_info->u.dwExemptDelta = url_entry->exempt_delta;
1319         entry_info->dwHeaderInfoSize = url_entry->header_info_size;
1320         entry_info->dwHitRate = url_entry->hit_rate;
1321         entry_info->dwSizeHigh = url_entry->size.u.HighPart;
1322         entry_info->dwSizeLow = url_entry->size.u.LowPart;
1323         entry_info->dwStructSize = sizeof(*entry_info);
1324         entry_info->dwUseCount = url_entry->use_count;
1325         dos_date_time_to_file_time(url_entry->expire_date, url_entry->expire_time, &entry_info->ExpireTime);
1326         entry_info->LastAccessTime = url_entry->access_time;
1327         entry_info->LastModifiedTime = url_entry->modification_time;
1328         dos_date_time_to_file_time(url_entry->sync_date, url_entry->sync_time, &entry_info->LastSyncTime);
1329     }
1330 
1331     if(unicode)
1332         url_len = urlcache_decode_url((const char*)url_entry+url_entry->url_off, NULL, 0);
1333     else
1334         url_len = strlen((LPCSTR)url_entry+url_entry->url_off) + 1;
1335     size += url_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1336 
1337     if(*info_size >= size) {
1338         DWORD url_size = url_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1339 
1340         entry_info->lpszSourceUrlName = (LPSTR)entry_info+size-url_size;
1341         if(unicode)
1342             urlcache_decode_url((const char*)url_entry+url_entry->url_off, (WCHAR*)entry_info->lpszSourceUrlName, url_len);
1343         else
1344             memcpy(entry_info->lpszSourceUrlName, (LPCSTR)url_entry+url_entry->url_off, url_size);
1345     }
1346 
1347     if(size%4 && size<*info_size)
1348         ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1349     size = DWORD_ALIGN(size);
1350 
1351     if(url_entry->local_name_off) {
1352         LONG file_name_size;
1353         LPSTR file_name;
1354         file_name = (LPSTR)entry_info+size;
1355         file_name_size = *info_size-size;
1356         if((unicode && urlcache_create_file_pathW(container, header, (LPCSTR)url_entry+url_entry->local_name_off,
1357                         url_entry->cache_dir, (LPWSTR)file_name, &file_name_size, FALSE)) ||
1358             (!unicode && urlcache_create_file_pathA(container, header, (LPCSTR)url_entry+url_entry->local_name_off,
1359                         url_entry->cache_dir, file_name, &file_name_size))) {
1360             entry_info->lpszLocalFileName = file_name;
1361         }
1362         size += file_name_size;
1363 
1364         if(size%4 && size<*info_size)
1365             ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1366         size = DWORD_ALIGN(size);
1367     }
1368 
1369     if(url_entry->header_info_off) {
1370         DWORD header_len;
1371 
1372         if(unicode)
1373             header_len = MultiByteToWideChar(CP_UTF8, 0, (const char*)url_entry+url_entry->header_info_off,
1374                     url_entry->header_info_size, NULL, 0);
1375         else
1376             header_len = url_entry->header_info_size;
1377         size += header_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1378 
1379         if(*info_size >= size) {
1380             DWORD header_size = header_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1381             entry_info->lpHeaderInfo = (LPBYTE)entry_info+size-header_size;
1382             if(unicode)
1383                 MultiByteToWideChar(CP_UTF8, 0, (const char*)url_entry+url_entry->header_info_off,
1384                         url_entry->header_info_size, (LPWSTR)entry_info->lpHeaderInfo, header_len);
1385             else
1386                 memcpy(entry_info->lpHeaderInfo, (LPCSTR)url_entry+url_entry->header_info_off, header_len);
1387         }
1388         if(size%4 && size<*info_size)
1389             ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1390         size = DWORD_ALIGN(size);
1391     }
1392 
1393     if(url_entry->file_extension_off) {
1394         int ext_len;
1395 
1396         if(unicode)
1397             ext_len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)url_entry+url_entry->file_extension_off, -1, NULL, 0);
1398         else
1399             ext_len = strlen((LPCSTR)url_entry+url_entry->file_extension_off) + 1;
1400         size += ext_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1401 
1402         if(*info_size >= size) {
1403             DWORD ext_size = ext_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1404             entry_info->lpszFileExtension = (LPSTR)entry_info+size-ext_size;
1405             if(unicode)
1406                 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)url_entry+url_entry->file_extension_off, -1, (LPWSTR)entry_info->lpszFileExtension, ext_len);
1407             else
1408                 memcpy(entry_info->lpszFileExtension, (LPCSTR)url_entry+url_entry->file_extension_off, ext_len*sizeof(CHAR));
1409         }
1410 
1411         if(size%4 && size<*info_size)
1412             ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1413         size = DWORD_ALIGN(size);
1414     }
1415 
1416     if(size > *info_size) {
1417         *info_size = size;
1418         return ERROR_INSUFFICIENT_BUFFER;
1419     }
1420     *info_size = size;
1421     return ERROR_SUCCESS;
1422 }
1423 
1424 /***********************************************************************
1425  *           urlcache_set_entry_info (Internal)
1426  *
1427  *  Helper for SetUrlCacheEntryInfo{A,W}. Sets fields in URL entry
1428  * according to the flags set by field_control.
1429  *
1430  * RETURNS
1431  *    ERROR_SUCCESS if the buffer was big enough
1432  *    ERROR_INSUFFICIENT_BUFFER if the buffer was too small
1433  *
1434  */
1435 static DWORD urlcache_set_entry_info(entry_url *url_entry, const INTERNET_CACHE_ENTRY_INFOA *entry_info, DWORD field_control)
1436 {
1437     if (field_control & CACHE_ENTRY_ACCTIME_FC)
1438         url_entry->access_time = entry_info->LastAccessTime;
1439     if (field_control & CACHE_ENTRY_ATTRIBUTE_FC)
1440         url_entry->cache_entry_type = entry_info->CacheEntryType;
1441     if (field_control & CACHE_ENTRY_EXEMPT_DELTA_FC)
1442         url_entry->exempt_delta = entry_info->u.dwExemptDelta;
1443     if (field_control & CACHE_ENTRY_EXPTIME_FC)
1444         file_time_to_dos_date_time(&entry_info->ExpireTime, &url_entry->expire_date, &url_entry->expire_time);
1445     if (field_control & CACHE_ENTRY_HEADERINFO_FC)
1446         FIXME("CACHE_ENTRY_HEADERINFO_FC unimplemented\n");
1447     if (field_control & CACHE_ENTRY_HITRATE_FC)
1448         url_entry->hit_rate = entry_info->dwHitRate;
1449     if (field_control & CACHE_ENTRY_MODTIME_FC)
1450         url_entry->modification_time = entry_info->LastModifiedTime;
1451     if (field_control & CACHE_ENTRY_SYNCTIME_FC)
1452         file_time_to_dos_date_time(&entry_info->LastAccessTime, &url_entry->sync_date, &url_entry->sync_time);
1453 
1454     return ERROR_SUCCESS;
1455 }
1456 
1457 /***********************************************************************
1458  *           urlcache_hash_key (Internal)
1459  *
1460  *  Returns the hash key for a given string
1461  *
1462  * RETURNS
1463  *    hash key for the string
1464  *
1465  */
1466 static DWORD urlcache_hash_key(LPCSTR lpszKey)
1467 {
1468     /* NOTE: this uses the same lookup table as SHLWAPI.UrlHash{A,W}
1469      * but the algorithm and result are not the same!
1470      */
1471     static const unsigned char lookupTable[256] =
1472     {
1473         0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77,
1474         0x8A, 0xAA, 0x7D, 0x76, 0x1B, 0xE9, 0x8C, 0x33,
1475         0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44,
1476         0x1E, 0x07, 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41,
1477         0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94, 0xDF,
1478         0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C,
1479         0x0C, 0xB5, 0x67, 0x46, 0x16, 0x3A, 0x4B, 0x4E,
1480         0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90,
1481         0xB0, 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53,
1482         0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6, 0x29, 0xFE,
1483         0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58,
1484         0x23, 0xCE, 0x5F, 0x74, 0xFC, 0xC0, 0x36, 0xDD,
1485         0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
1486         0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D,
1487         0xA6, 0x50, 0x32, 0x22, 0xAF, 0xC3, 0x64, 0x63,
1488         0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD,
1489         0x79, 0x40, 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A,
1490         0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9, 0xC2,
1491         0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B,
1492         0x4A, 0x3B, 0x89, 0xE4, 0x6C, 0xBF, 0xE8, 0x8B,
1493         0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C,
1494         0xFB, 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70,
1495         0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB, 0x0D, 0x20,
1496         0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B,
1497         0xF9, 0xEC, 0x2D, 0xF4, 0x6F, 0xB6, 0x99, 0x88,
1498         0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
1499         0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72,
1500         0xA2, 0x35, 0xA0, 0xD7, 0xCD, 0xB4, 0x2F, 0x6D,
1501         0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34,
1502         0x3F, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8,
1503         0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB, 0x0A,
1504         0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1
1505     };
1506     BYTE key[4];
1507     DWORD i;
1508 
1509     for (i = 0; i < ARRAY_SIZE(key); i++)
1510         key[i] = lookupTable[(*lpszKey + i) & 0xFF];
1511 
1512     for (lpszKey++; *lpszKey; lpszKey++)
1513     {
1514         for (i = 0; i < ARRAY_SIZE(key); i++)
1515             key[i] = lookupTable[*lpszKey ^ key[i]];
1516     }
1517 
1518     return *(DWORD *)key;
1519 }
1520 
1521 static inline entry_hash_table* urlcache_get_hash_table(const urlcache_header *pHeader, DWORD dwOffset)
1522 {
1523     if(!dwOffset)
1524         return NULL;
1525     return (entry_hash_table*)((LPBYTE)pHeader + dwOffset);
1526 }
1527 
1528 static BOOL urlcache_find_hash_entry(const urlcache_header *pHeader, LPCSTR lpszUrl, struct hash_entry **ppHashEntry)
1529 {
1530     /* structure of hash table:
1531      *  448 entries divided into 64 blocks
1532      *  each block therefore contains a chain of 7 key/offset pairs
1533      * how position in table is calculated:
1534      *  1. the url is hashed in helper function
1535      *  2. the key % HASHTABLE_NUM_ENTRIES is the bucket number
1536      *  3. bucket number * HASHTABLE_BLOCKSIZE is offset of the bucket
1537      *
1538      * note:
1539      *  there can be multiple hash tables in the file and the offset to
1540      *  the next one is stored in the header of the hash table
1541      */
1542     DWORD key = urlcache_hash_key(lpszUrl);
1543     DWORD offset = (key & (HASHTABLE_NUM_ENTRIES-1)) * HASHTABLE_BLOCKSIZE;
1544     entry_hash_table* pHashEntry;
1545     DWORD id = 0;
1546 
1547     key >>= HASHTABLE_FLAG_BITS;
1548 
1549     for (pHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1550          pHashEntry; pHashEntry = urlcache_get_hash_table(pHeader, pHashEntry->next))
1551     {
1552         int i;
1553         if (pHashEntry->id != id++)
1554         {
1555             ERR("Error: not right hash table number (%d) expected %d\n", pHashEntry->id, id);
1556             continue;
1557         }
1558         /* make sure that it is in fact a hash entry */
1559         if (pHashEntry->header.signature != HASH_SIGNATURE)
1560         {
1561             ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->header.signature);
1562             continue;
1563         }
1564 
1565         for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
1566         {
1567             struct hash_entry *pHashElement = &pHashEntry->hash_table[offset + i];
1568             if (key == pHashElement->key>>HASHTABLE_FLAG_BITS)
1569             {
1570                 /* FIXME: we should make sure that this is the right element
1571                  * before returning and claiming that it is. We can do this
1572                  * by doing a simple compare between the URL we were given
1573                  * and the URL stored in the entry. However, this assumes
1574                  * we know the format of all the entries stored in the
1575                  * hash table */
1576                 *ppHashEntry = pHashElement;
1577                 return TRUE;
1578             }
1579         }
1580     }
1581     return FALSE;
1582 }
1583 
1584 /***********************************************************************
1585  *           urlcache_hash_entry_set_flags (Internal)
1586  *
1587  *  Sets special bits in hash key
1588  *
1589  * RETURNS
1590  *    nothing
1591  *
1592  */
1593 static void urlcache_hash_entry_set_flags(struct hash_entry *pHashEntry, DWORD dwFlag)
1594 {
1595     pHashEntry->key = (pHashEntry->key >> HASHTABLE_FLAG_BITS << HASHTABLE_FLAG_BITS) | dwFlag;
1596 }
1597 
1598 /***********************************************************************
1599  *           urlcache_hash_entry_delete (Internal)
1600  *
1601  *  Searches all the hash tables in the index for the given URL and
1602  * then if found deletes the entry.
1603  *
1604  * RETURNS
1605  *    TRUE if the entry was found
1606  *    FALSE if the entry could not be found
1607  *
1608  */
1609 static BOOL urlcache_hash_entry_delete(struct hash_entry *pHashEntry)
1610 {
1611     pHashEntry->key = HASHTABLE_DEL;
1612     return TRUE;
1613 }
1614 
1615 /***********************************************************************
1616  *           urlcache_hash_entry_create (Internal)
1617  *
1618  *  Searches all the hash tables for a free slot based on the offset
1619  * generated from the hash key. If a free slot is found, the offset and
1620  * key are entered into the hash table.
1621  *
1622  * RETURNS
1623  *    ERROR_SUCCESS if the entry was added
1624  *    Any other Win32 error code if the entry could not be added
1625  *
1626  */
1627 static DWORD urlcache_hash_entry_create(urlcache_header *pHeader, LPCSTR lpszUrl, DWORD dwOffsetEntry, DWORD dwFieldType)
1628 {
1629     /* see urlcache_find_hash_entry for structure of hash tables */
1630 
1631     DWORD key = urlcache_hash_key(lpszUrl);
1632     DWORD offset = (key & (HASHTABLE_NUM_ENTRIES-1)) * HASHTABLE_BLOCKSIZE;
1633     entry_hash_table* pHashEntry, *pHashPrev = NULL;
1634     DWORD id = 0;
1635     DWORD error;
1636 
1637     key = ((key >> HASHTABLE_FLAG_BITS) << HASHTABLE_FLAG_BITS) + dwFieldType;
1638 
1639     for (pHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1640          pHashEntry; pHashEntry = urlcache_get_hash_table(pHeader, pHashEntry->next))
1641     {
1642         int i;
1643         pHashPrev = pHashEntry;
1644 
1645         if (pHashEntry->id != id++)
1646         {
1647             ERR("not right hash table number (%d) expected %d\n", pHashEntry->id, id);
1648             break;
1649         }
1650         /* make sure that it is in fact a hash entry */
1651         if (pHashEntry->header.signature != HASH_SIGNATURE)
1652         {
1653             ERR("not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->header.signature);
1654             break;
1655         }
1656 
1657         for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
1658         {
1659             struct hash_entry *pHashElement = &pHashEntry->hash_table[offset + i];
1660             if (pHashElement->key==HASHTABLE_FREE || pHashElement->key==HASHTABLE_DEL) /* if the slot is free */
1661             {
1662                 pHashElement->key = key;
1663                 pHashElement->offset = dwOffsetEntry;
1664                 return ERROR_SUCCESS;
1665             }
1666         }
1667     }
1668     error = urlcache_create_hash_table(pHeader, pHashPrev, &pHashEntry);
1669     if (error != ERROR_SUCCESS)
1670         return error;
1671 
1672     pHashEntry->hash_table[offset].key = key;
1673     pHashEntry->hash_table[offset].offset = dwOffsetEntry;
1674     return ERROR_SUCCESS;
1675 }
1676 
1677 /***********************************************************************
1678  *           urlcache_enum_hash_tables (Internal)
1679  *
1680  *  Enumerates the hash tables in a container.
1681  *
1682  * RETURNS
1683  *    TRUE if an entry was found
1684  *    FALSE if there are no more tables to enumerate.
1685  *
1686  */
1687 static BOOL urlcache_enum_hash_tables(const urlcache_header *pHeader, DWORD *id, entry_hash_table **ppHashEntry)
1688 {
1689     for (*ppHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1690          *ppHashEntry; *ppHashEntry = urlcache_get_hash_table(pHeader, (*ppHashEntry)->next))
1691     {
1692         TRACE("looking at hash table number %d\n", (*ppHashEntry)->id);
1693         if ((*ppHashEntry)->id != *id)
1694             continue;
1695         /* make sure that it is in fact a hash entry */
1696         if ((*ppHashEntry)->header.signature != HASH_SIGNATURE)
1697         {
1698             ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&(*ppHashEntry)->header.signature);
1699             (*id)++;
1700             continue;
1701         }
1702 
1703         TRACE("hash table number %d found\n", *id);
1704         return TRUE;
1705     }
1706     return FALSE;
1707 }
1708 
1709 /***********************************************************************
1710  *           urlcache_enum_hash_table_entries (Internal)
1711  *
1712  *  Enumerates entries in a hash table and returns the next non-free entry.
1713  *
1714  * RETURNS
1715  *    TRUE if an entry was found
1716  *    FALSE if the hash table is empty or there are no more entries to
1717  *     enumerate.
1718  *
1719  */
1720 static BOOL urlcache_enum_hash_table_entries(const urlcache_header *pHeader, const entry_hash_table *pHashEntry,
1721                                           DWORD * index, const struct hash_entry **ppHashEntry)
1722 {
1723     for (; *index < HASHTABLE_SIZE ; (*index)++)
1724     {
1725         if (pHashEntry->hash_table[*index].key==HASHTABLE_FREE || pHashEntry->hash_table[*index].key==HASHTABLE_DEL)
1726             continue;
1727 
1728         *ppHashEntry = &pHashEntry->hash_table[*index];
1729         TRACE("entry found %d\n", *index);
1730         return TRUE;
1731     }
1732     TRACE("no more entries (%d)\n", *index);
1733     return FALSE;
1734 }
1735 
1736 /***********************************************************************
1737  *           cache_container_delete_dir (Internal)
1738  *
1739  *  Erase a directory containing an URL cache.
1740  *
1741  * RETURNS
1742  *    TRUE success, FALSE failure/aborted.
1743  *
1744  */
1745 static BOOL cache_container_delete_dir(LPCWSTR lpszPath)
1746 {
1747     DWORD path_len;
1748     WCHAR path[MAX_PATH + 1];
1749     SHFILEOPSTRUCTW shfos;
1750     int ret;
1751 
1752     path_len = strlenW(lpszPath);
1753     if (path_len >= MAX_PATH)
1754         return FALSE;
1755     strcpyW(path, lpszPath);
1756     path[path_len + 1] = 0;  /* double-NUL-terminate path */
1757 
1758     shfos.hwnd = NULL;
1759     shfos.wFunc = FO_DELETE;
1760     shfos.pFrom = path;
1761     shfos.pTo = NULL;
1762     shfos.fFlags = FOF_NOCONFIRMATION;
1763     shfos.fAnyOperationsAborted = FALSE;
1764     ret = SHFileOperationW(&shfos);
1765     if (ret)
1766         ERR("SHFileOperationW on %s returned %i\n", debugstr_w(path), ret);
1767     return !(ret || shfos.fAnyOperationsAborted);
1768 }
1769 
1770 /***********************************************************************
1771  *           urlcache_hash_entry_is_locked (Internal)
1772  *
1773  *  Checks if entry is locked. Unlocks it if possible.
1774  */
1775 static BOOL urlcache_hash_entry_is_locked(struct hash_entry *hash_entry, entry_url *url_entry)
1776 {
1777     FILETIME cur_time;
1778     ULARGE_INTEGER acc_time, time;
1779 
1780     if ((hash_entry->key & ((1<<HASHTABLE_FLAG_BITS)-1)) != HASHTABLE_LOCK)
1781         return FALSE;
1782 
1783     GetSystemTimeAsFileTime(&cur_time);
1784     time.u.LowPart = cur_time.dwLowDateTime;
1785     time.u.HighPart = cur_time.dwHighDateTime;
1786 
1787     acc_time.u.LowPart = url_entry->access_time.dwLowDateTime;
1788     acc_time.u.HighPart = url_entry->access_time.dwHighDateTime;
1789 
1790     time.QuadPart -= acc_time.QuadPart;
1791 
1792     /* check if entry was locked for at least a day */
1793     if(time.QuadPart > (ULONGLONG)24*60*60*FILETIME_SECOND) {
1794         urlcache_hash_entry_set_flags(hash_entry, HASHTABLE_URL);
1795         url_entry->use_count = 0;
1796         return FALSE;
1797     }
1798 
1799     return TRUE;
1800 }
1801 
1802 static BOOL urlcache_get_entry_info(const char *url, void *entry_info,
1803         DWORD *size, DWORD flags, BOOL unicode)
1804 {
1805     urlcache_header *header;
1806     struct hash_entry *hash_entry;
1807     const entry_url *url_entry;
1808     cache_container *container;
1809     DWORD error;
1810 
1811     TRACE("(%s, %p, %p, %x, %x)\n", debugstr_a(url), entry_info, size, flags, unicode);
1812 
1813     if(flags & ~GET_INSTALLED_ENTRY)
1814         FIXME("ignoring unsupported flags: %x\n", flags);
1815 
1816     error = cache_containers_find(url, &container);
1817     if(error != ERROR_SUCCESS) {
1818         SetLastError(error);
1819         return FALSE;
1820     }
1821 
1822     error = cache_container_open_index(container, MIN_BLOCK_NO);
1823     if(error != ERROR_SUCCESS) {
1824         SetLastError(error);
1825         return FALSE;
1826     }
1827 
1828     if(!(header = cache_container_lock_index(container)))
1829         return FALSE;
1830 
1831     if(!urlcache_find_hash_entry(header, url, &hash_entry)) {
1832         cache_container_unlock_index(container, header);
1833         WARN("entry %s not found!\n", debugstr_a(url));
1834         SetLastError(ERROR_FILE_NOT_FOUND);
1835         return FALSE;
1836     }
1837 
1838     url_entry = (const entry_url*)((LPBYTE)header + hash_entry->offset);
1839     if(url_entry->header.signature != URL_SIGNATURE) {
1840         cache_container_unlock_index(container, header);
1841         FIXME("Trying to retrieve entry of unknown format %s\n",
1842                 debugstr_an((LPCSTR)&url_entry->header.signature, sizeof(DWORD)));
1843         SetLastError(ERROR_FILE_NOT_FOUND);
1844         return FALSE;
1845     }
1846 
1847     TRACE("Found URL: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->url_off));
1848     TRACE("Header info: %s\n", debugstr_an((LPCSTR)url_entry +
1849                 url_entry->header_info_off, url_entry->header_info_size));
1850 
1851     if((flags & GET_INSTALLED_ENTRY) && !(url_entry->cache_entry_type & INSTALLED_CACHE_ENTRY)) {
1852         cache_container_unlock_index(container, header);
1853         SetLastError(ERROR_FILE_NOT_FOUND);
1854         return FALSE;
1855     }
1856 
1857     if(size) {
1858         if(!entry_info)
1859             *size = 0;
1860 
1861         error = urlcache_copy_entry(container, header, entry_info, size, url_entry, unicode);
1862         if(error != ERROR_SUCCESS) {
1863             cache_container_unlock_index(container, header);
1864             SetLastError(error);
1865             return FALSE;
1866         }
1867         if(url_entry->local_name_off)
1868             TRACE("Local File Name: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->local_name_off));
1869     }
1870 
1871     cache_container_unlock_index(container, header);
1872     return TRUE;
1873 }
1874 
1875 /***********************************************************************
1876  *           GetUrlCacheEntryInfoExA (WININET.@)
1877  *
1878  */
1879 BOOL WINAPI GetUrlCacheEntryInfoExA(LPCSTR lpszUrl,
1880         LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1881         LPDWORD lpdwCacheEntryInfoBufSize, LPSTR lpszReserved,
1882         LPDWORD lpdwReserved, LPVOID lpReserved, DWORD dwFlags)
1883 {
1884     if(lpszReserved!=NULL || lpdwReserved!=NULL || lpReserved!=NULL) {
1885         ERR("Reserved value was not 0\n");
1886         SetLastError(ERROR_INVALID_PARAMETER);
1887         return FALSE;
1888     }
1889 
1890     return urlcache_get_entry_info(lpszUrl, lpCacheEntryInfo,
1891             lpdwCacheEntryInfoBufSize, dwFlags, FALSE);
1892 }
1893 
1894 /***********************************************************************
1895  *           GetUrlCacheEntryInfoA (WININET.@)
1896  *
1897  */
1898 BOOL WINAPI GetUrlCacheEntryInfoA(LPCSTR lpszUrlName,
1899     LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1900     LPDWORD lpdwCacheEntryInfoBufferSize)
1901 {
1902     return GetUrlCacheEntryInfoExA(lpszUrlName, lpCacheEntryInfo,
1903             lpdwCacheEntryInfoBufferSize, NULL, NULL, NULL, 0);
1904 }
1905 
1906 static int urlcache_encode_url(const WCHAR *url, char *encoded_url, int encoded_len)
1907 {
1908     URL_COMPONENTSW uc;
1909     DWORD len, part_len;
1910     WCHAR *punycode;
1911 
1912     TRACE("%s\n", debugstr_w(url));
1913 
1914     memset(&uc, 0, sizeof(uc));
1915     uc.dwStructSize = sizeof(uc);
1916     uc.dwHostNameLength = 1;
1917     if(!InternetCrackUrlW(url, 0, 0, &uc))
1918         uc.nScheme = INTERNET_SCHEME_UNKNOWN;
1919 
1920     if(uc.nScheme!=INTERNET_SCHEME_HTTP && uc.nScheme!=INTERNET_SCHEME_HTTPS)
1921         return WideCharToMultiByte(CP_UTF8, 0, url, -1, encoded_url, encoded_len, NULL, NULL);
1922 
1923     len = WideCharToMultiByte(CP_UTF8, 0, url, uc.lpszHostName-url,
1924             encoded_url, encoded_len, NULL, NULL);
1925     if(!len)
1926         return 0;
1927     if(encoded_url)
1928         encoded_len -= len;
1929 
1930     part_len = IdnToAscii(0, uc.lpszHostName, uc.dwHostNameLength, NULL, 0);
1931     if(!part_len) {
1932         SetLastError(ERROR_INTERNET_INVALID_URL);
1933         return 0;
1934     }
1935 
1936     punycode = heap_alloc(part_len*sizeof(WCHAR));
1937     if(!punycode)
1938         return 0;
1939 
1940     part_len = IdnToAscii(0, uc.lpszHostName, uc.dwHostNameLength, punycode, part_len);
1941     if(!part_len) {
1942         heap_free(punycode);
1943         return 0;
1944     }
1945 
1946     part_len = WideCharToMultiByte(CP_UTF8, 0, punycode, part_len,
1947             encoded_url ? encoded_url+len : NULL, encoded_len, NULL, NULL);
1948     heap_free(punycode);
1949     if(!part_len)
1950         return 0;
1951     if(encoded_url)
1952         encoded_len -= part_len;
1953     len += part_len;
1954 
1955     part_len = WideCharToMultiByte(CP_UTF8, 0, uc.lpszHostName+uc.dwHostNameLength,
1956             -1, encoded_url ? encoded_url+len : NULL, encoded_len, NULL, NULL);
1957     if(!part_len)
1958         return 0;
1959     len += part_len;
1960 
1961     TRACE("got (%d)%s\n", len, debugstr_a(encoded_url));
1962     return len;
1963 }
1964 
1965 static BOOL urlcache_encode_url_alloc(const WCHAR *url, char **encoded_url)
1966 {
1967     DWORD encoded_len;
1968     char *ret;
1969 
1970     encoded_len = urlcache_encode_url(url, NULL, 0);
1971     if(!encoded_len)
1972         return FALSE;
1973 
1974     ret = heap_alloc(encoded_len*sizeof(WCHAR));
1975     if(!ret)
1976         return FALSE;
1977 
1978     encoded_len = urlcache_encode_url(url, ret, encoded_len);
1979     if(!encoded_len) {
1980         heap_free(ret);
1981         return FALSE;
1982     }
1983 
1984     *encoded_url = ret;
1985     return TRUE;
1986 }
1987 
1988 /***********************************************************************
1989  *           GetUrlCacheEntryInfoExW (WININET.@)
1990  *
1991  */
1992 BOOL WINAPI GetUrlCacheEntryInfoExW(LPCWSTR lpszUrl,
1993         LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
1994         LPDWORD lpdwCacheEntryInfoBufSize, LPWSTR lpszReserved,
1995         LPDWORD lpdwReserved, LPVOID lpReserved, DWORD dwFlags)
1996 {
1997     char *url;
1998     BOOL ret;
1999 
2000     if(lpszReserved!=NULL || lpdwReserved!=NULL || lpReserved!=NULL) {
2001         ERR("Reserved value was not 0\n");
2002         SetLastError(ERROR_INVALID_PARAMETER);
2003         return FALSE;
2004     }
2005 
2006     /* Ignore GET_INSTALLED_ENTRY flag in unicode version of function */
2007     dwFlags &= ~GET_INSTALLED_ENTRY;
2008 
2009     if(!urlcache_encode_url_alloc(lpszUrl, &url))
2010         return FALSE;
2011 
2012     ret = urlcache_get_entry_info(url, lpCacheEntryInfo,
2013             lpdwCacheEntryInfoBufSize, dwFlags, TRUE);
2014     heap_free(url);
2015     return ret;
2016 }
2017 
2018 /***********************************************************************
2019  *           GetUrlCacheEntryInfoW (WININET.@)
2020  *
2021  */
2022 BOOL WINAPI GetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
2023         LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2024         LPDWORD lpdwCacheEntryInfoBufferSize)
2025 {
2026     return GetUrlCacheEntryInfoExW(lpszUrl, lpCacheEntryInfo,
2027             lpdwCacheEntryInfoBufferSize, NULL, NULL, NULL, 0);
2028 }
2029 
2030 /***********************************************************************
2031  *           SetUrlCacheEntryInfoA (WININET.@)
2032  */
2033 BOOL WINAPI SetUrlCacheEntryInfoA(LPCSTR lpszUrlName,
2034         LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
2035         DWORD dwFieldControl)
2036 {
2037     urlcache_header *pHeader;
2038     struct hash_entry *pHashEntry;
2039     entry_header *pEntry;
2040     cache_container *pContainer;
2041     DWORD error;
2042 
2043     TRACE("(%s, %p, 0x%08x)\n", debugstr_a(lpszUrlName), lpCacheEntryInfo, dwFieldControl);
2044 
2045     error = cache_containers_find(lpszUrlName, &pContainer);
2046     if (error != ERROR_SUCCESS)
2047     {
2048         SetLastError(error);
2049         return FALSE;
2050     }
2051 
2052     error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
2053     if (error != ERROR_SUCCESS)
2054     {
2055         SetLastError(error);
2056         return FALSE;
2057     }
2058 
2059     if (!(pHeader = cache_container_lock_index(pContainer)))
2060         return FALSE;
2061 
2062     if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
2063     {
2064         cache_container_unlock_index(pContainer, pHeader);
2065         WARN("entry %s not found!\n", debugstr_a(lpszUrlName));
2066         SetLastError(ERROR_FILE_NOT_FOUND);
2067         return FALSE;
2068     }
2069 
2070     pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2071     if (pEntry->signature != URL_SIGNATURE)
2072     {
2073         cache_container_unlock_index(pContainer, pHeader);
2074         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->signature, sizeof(DWORD)));
2075         SetLastError(ERROR_FILE_NOT_FOUND);
2076         return FALSE;
2077     }
2078 
2079     urlcache_set_entry_info((entry_url*)pEntry, lpCacheEntryInfo, dwFieldControl);
2080 
2081     cache_container_unlock_index(pContainer, pHeader);
2082 
2083     return TRUE;
2084 }
2085 
2086 /***********************************************************************
2087  *           SetUrlCacheEntryInfoW (WININET.@)
2088  */
2089 BOOL WINAPI SetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
2090         LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2091         DWORD dwFieldControl)
2092 {
2093     char *url;
2094     BOOL ret;
2095 
2096     if(!urlcache_encode_url_alloc(lpszUrl, &url))
2097         return FALSE;
2098 
2099     ret = SetUrlCacheEntryInfoA(url, (INTERNET_CACHE_ENTRY_INFOA*)lpCacheEntryInfo, dwFieldControl);
2100     heap_free(url);
2101     return ret;
2102 }
2103 
2104 static BOOL urlcache_entry_get_file(const char *url, void *entry_info, DWORD *size, BOOL unicode)
2105 {
2106     urlcache_header *header;
2107     struct hash_entry *hash_entry;
2108     entry_url *url_entry;
2109     cache_container *container;
2110     DWORD error;
2111 
2112     TRACE("(%s, %p, %p, %x)\n", debugstr_a(url), entry_info, size, unicode);
2113 
2114     if(!url || !size || (!entry_info && *size)) {
2115         SetLastError(ERROR_INVALID_PARAMETER);
2116         return FALSE;
2117     }
2118 
2119     error = cache_containers_find(url, &container);
2120     if(error != ERROR_SUCCESS) {
2121         SetLastError(error);
2122         return FALSE;
2123     }
2124 
2125     error = cache_container_open_index(container, MIN_BLOCK_NO);
2126     if (error != ERROR_SUCCESS) {
2127         SetLastError(error);
2128         return FALSE;
2129     }
2130 
2131     if (!(header = cache_container_lock_index(container)))
2132         return FALSE;
2133 
2134     if (!urlcache_find_hash_entry(header, url, &hash_entry)) {
2135         cache_container_unlock_index(container, header);
2136         TRACE("entry %s not found!\n", debugstr_a(url));
2137         SetLastError(ERROR_FILE_NOT_FOUND);
2138         return FALSE;
2139     }
2140 
2141     url_entry = (entry_url*)((LPBYTE)header + hash_entry->offset);
2142     if(url_entry->header.signature != URL_SIGNATURE) {
2143         cache_container_unlock_index(container, header);
2144         FIXME("Trying to retrieve entry of unknown format %s\n",
2145                 debugstr_an((LPSTR)&url_entry->header.signature, sizeof(DWORD)));
2146         SetLastError(ERROR_FILE_NOT_FOUND);
2147         return FALSE;
2148     }
2149 
2150     if(!url_entry->local_name_off) {
2151         cache_container_unlock_index(container, header);
2152         SetLastError(ERROR_INVALID_DATA);
2153         return FALSE;
2154     }
2155 
2156     TRACE("Found URL: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->url_off));
2157     TRACE("Header info: %s\n", debugstr_an((LPCSTR)url_entry + url_entry->header_info_off,
2158                 url_entry->header_info_size));
2159 
2160     error = urlcache_copy_entry(container, header, entry_info,
2161             size, url_entry, unicode);
2162     if(error != ERROR_SUCCESS) {
2163         cache_container_unlock_index(container, header);
2164         SetLastError(error);
2165         return FALSE;
2166     }
2167     TRACE("Local File Name: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->local_name_off));
2168 
2169     url_entry->hit_rate++;
2170     url_entry->use_count++;
2171     urlcache_hash_entry_set_flags(hash_entry, HASHTABLE_LOCK);
2172     GetSystemTimeAsFileTime(&url_entry->access_time);
2173 
2174     cache_container_unlock_index(container, header);
2175 
2176     return TRUE;
2177 }
2178 
2179 /***********************************************************************
2180  *           RetrieveUrlCacheEntryFileA (WININET.@)
2181  *
2182  */
2183 BOOL WINAPI RetrieveUrlCacheEntryFileA(LPCSTR lpszUrlName,
2184         LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
2185         LPDWORD lpdwCacheEntryInfoBufferSize, DWORD dwReserved)
2186 {
2187     return urlcache_entry_get_file(lpszUrlName, lpCacheEntryInfo,
2188             lpdwCacheEntryInfoBufferSize, FALSE);
2189 }
2190 
2191 /***********************************************************************
2192  *           RetrieveUrlCacheEntryFileW (WININET.@)
2193  *
2194  */
2195 BOOL WINAPI RetrieveUrlCacheEntryFileW(LPCWSTR lpszUrlName,
2196         LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2197         LPDWORD lpdwCacheEntryInfoBufferSize, DWORD dwReserved)
2198 {
2199     char *url;
2200     BOOL ret;
2201 
2202     if(!urlcache_encode_url_alloc(lpszUrlName, &url))
2203         return FALSE;
2204 
2205     ret = urlcache_entry_get_file(url, lpCacheEntryInfo,
2206             lpdwCacheEntryInfoBufferSize, TRUE);
2207     heap_free(url);
2208     return ret;
2209 }
2210 
2211 static BOOL urlcache_entry_delete(const cache_container *pContainer,
2212         urlcache_header *pHeader, struct hash_entry *pHashEntry)
2213 {
2214     entry_header *pEntry;
2215     entry_url * pUrlEntry;
2216 
2217     pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2218     if (pEntry->signature != URL_SIGNATURE)
2219     {
2220         FIXME("Trying to delete entry of unknown format %s\n",
2221               debugstr_an((LPCSTR)&pEntry->signature, sizeof(DWORD)));
2222         SetLastError(ERROR_FILE_NOT_FOUND);
2223         return FALSE;
2224     }
2225 
2226     pUrlEntry = (entry_url *)pEntry;
2227     if(urlcache_hash_entry_is_locked(pHashEntry, pUrlEntry))
2228     {
2229         TRACE("Trying to delete locked entry\n");
2230         pUrlEntry->cache_entry_type |= PENDING_DELETE_CACHE_ENTRY;
2231         SetLastError(ERROR_SHARING_VIOLATION);
2232         return FALSE;
2233     }
2234 
2235     if(!urlcache_delete_file(pContainer, pHeader, pUrlEntry))
2236     {
2237         urlcache_entry_free(pHeader, pEntry);
2238     }
2239     else
2240     {
2241         /* Add entry to leaked files list */
2242         pUrlEntry->header.signature = LEAK_SIGNATURE;
2243         pUrlEntry->exempt_delta = pHeader->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET];
2244         pHeader->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET] = pHashEntry->offset;
2245     }
2246 
2247     urlcache_hash_entry_delete(pHashEntry);
2248     return TRUE;
2249 }
2250 
2251 static HANDLE free_cache_running;
2252 static HANDLE dll_unload_event;
2253 static DWORD WINAPI handle_full_cache_worker(void *param)
2254 {
2255     FreeUrlCacheSpaceW(NULL, 20, 0);
2256     ReleaseSemaphore(free_cache_running, 1, NULL);
2257     return 0;
2258 }
2259 
2260 static void handle_full_cache(void)
2261 {
2262     if(WaitForSingleObject(free_cache_running, 0) == WAIT_OBJECT_0) {
2263         if(!QueueUserWorkItem(handle_full_cache_worker, NULL, 0))
2264             ReleaseSemaphore(free_cache_running, 1, NULL);
2265     }
2266 }
2267 
2268 /* Enumerates entries in cache, allows cache unlocking between calls. */
2269 static BOOL urlcache_next_entry(urlcache_header *header, DWORD *hash_table_off, DWORD *hash_table_entry,
2270         struct hash_entry **hash_entry, entry_header **entry)
2271 {
2272     entry_hash_table *hashtable_entry;
2273 
2274     *hash_entry = NULL;
2275     *entry = NULL;
2276 
2277     if(!*hash_table_off) {
2278         *hash_table_off = header->hash_table_off;
2279         *hash_table_entry = 0;
2280 
2281         hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2282     }else {
2283         if(*hash_table_off >= header->size) {
2284             *hash_table_off = 0;
2285             return FALSE;
2286         }
2287 
2288         hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2289     }
2290 
2291     if(hashtable_entry->header.signature != HASH_SIGNATURE) {
2292         *hash_table_off = 0;
2293         return FALSE;
2294     }
2295 
2296     while(1) {
2297         if(*hash_table_entry >= HASHTABLE_SIZE) {
2298             *hash_table_off = hashtable_entry->next;
2299             if(!*hash_table_off) {
2300                 *hash_table_off = 0;
2301                 return FALSE;
2302             }
2303 
2304             hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2305             *hash_table_entry = 0;
2306         }
2307 
2308         if(hashtable_entry->hash_table[*hash_table_entry].key != HASHTABLE_DEL &&
2309             hashtable_entry->hash_table[*hash_table_entry].key != HASHTABLE_FREE) {
2310             *hash_entry = &hashtable_entry->hash_table[*hash_table_entry];
2311             *entry = (entry_header*)((LPBYTE)header + hashtable_entry->hash_table[*hash_table_entry].offset);
2312             (*hash_table_entry)++;
2313             return TRUE;
2314         }
2315 
2316         (*hash_table_entry)++;
2317     }
2318 
2319     *hash_table_off = 0;
2320     return FALSE;
2321 }
2322 
2323 /* Rates an urlcache entry to determine if it can be deleted.
2324  *
2325  * Score 0 means that entry can safely be removed, the bigger rating
2326  * the smaller chance of entry being removed.
2327  * DWORD_MAX means that entry can't be deleted at all.
2328  *
2329  * Rating system is currently not fully compatible with native implementation.
2330  */
2331 static DWORD urlcache_rate_entry(entry_url *url_entry, FILETIME *cur_time)
2332 {
2333     ULARGE_INTEGER time, access_time;
2334     DWORD rating;
2335 
2336     access_time.u.LowPart = url_entry->access_time.dwLowDateTime;
2337     access_time.u.HighPart = url_entry->access_time.dwHighDateTime;
2338 
2339     time.u.LowPart = cur_time->dwLowDateTime;
2340     time.u.HighPart = cur_time->dwHighDateTime;
2341 
2342     /* Don't touch entries that were added less than 10 minutes ago */
2343     if(time.QuadPart < access_time.QuadPart + (ULONGLONG)10*60*FILETIME_SECOND)
2344         return -1;
2345 
2346     if(url_entry->cache_entry_type & STICKY_CACHE_ENTRY)
2347         if(time.QuadPart < access_time.QuadPart + (ULONGLONG)url_entry->exempt_delta*FILETIME_SECOND)
2348             return -1;
2349 
2350     time.QuadPart = (time.QuadPart-access_time.QuadPart)/FILETIME_SECOND;
2351     rating = 400*60*60*24/(60*60*24+time.QuadPart);
2352 
2353     if(url_entry->hit_rate > 100)
2354         rating += 100;
2355     else
2356         rating += url_entry->hit_rate;
2357 
2358     return rating;
2359 }
2360 
2361 static int dword_cmp(const void *p1, const void *p2)
2362 {
2363     return *(const DWORD*)p1 - *(const DWORD*)p2;
2364 }
2365 
2366 /***********************************************************************
2367  *           FreeUrlCacheSpaceW (WININET.@)
2368  *
2369  * Frees up some cache.
2370  *
2371  * PARAMETERS
2372  *   cache_path    [I] Which volume to free up from, or NULL if you don't care.
2373  *   size          [I] Percentage of the cache that should be free.
2374  *   filter        [I] Which entries can't be deleted (CacheEntryType)
2375  *
2376  * RETURNS
2377  *   TRUE success. FALSE failure.
2378  *
2379  * IMPLEMENTATION
2380  *   This implementation just retrieves the path of the cache directory, and
2381  *   deletes its contents from the filesystem. The correct approach would
2382  *   probably be to implement and use {FindFirst,FindNext,Delete}UrlCacheGroup().
2383  */
2384 BOOL WINAPI FreeUrlCacheSpaceW(LPCWSTR cache_path, DWORD size, DWORD filter)
2385 {
2386     cache_container *container;
2387     DWORD path_len, err;
2388 
2389     TRACE("(%s, %x, %x)\n", debugstr_w(cache_path), size, filter);
2390 
2391     if(size<1 || size>100) {
2392         SetLastError(ERROR_INVALID_PARAMETER);
2393         return FALSE;
2394     }
2395 
2396     if(cache_path) {
2397         path_len = strlenW(cache_path);
2398         if(cache_path[path_len-1] == '\\')
2399             path_len--;
2400     }else {
2401         path_len = 0;
2402     }
2403 
2404     if(size==100 && !filter) {
2405         LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
2406         {
2407             /* When cache_path==NULL only clean Temporary Internet Files */
2408             if((!path_len && container->cache_prefix[0]==0) ||
2409                     (path_len && !strncmpiW(container->path, cache_path, path_len) &&
2410                      (container->path[path_len]=='\0' || container->path[path_len]=='\\')))
2411             {
2412                 BOOL ret_del;
2413 
2414                 WaitForSingleObject(container->mutex, INFINITE);
2415 
2416                 /* unlock, delete, recreate and lock cache */
2417                 cache_container_close_index(container);
2418                 ret_del = cache_container_delete_dir(container->path);
2419                 err = cache_container_open_index(container, MIN_BLOCK_NO);
2420 
2421                 ReleaseMutex(container->mutex);
2422                 if(!ret_del || (err != ERROR_SUCCESS))
2423                     return FALSE;
2424             }
2425         }
2426 
2427         return TRUE;
2428     }
2429 
2430     LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
2431     {
2432         urlcache_header *header;
2433         struct hash_entry *hash_entry;
2434         entry_header *entry;
2435         entry_url *url_entry;
2436         ULONGLONG desired_size, cur_size;
2437         DWORD delete_factor, hash_table_off, hash_table_entry;
2438         DWORD rate[100], rate_no;
2439         FILETIME cur_time;
2440 
2441         if((path_len || container->cache_prefix[0]!=0) &&
2442                 (!path_len || strncmpiW(container->path, cache_path, path_len) ||
2443                  (container->path[path_len]!='\0' && container->path[path_len]!='\\')))
2444             continue;
2445 
2446         err = cache_container_open_index(container, MIN_BLOCK_NO);
2447         if(err != ERROR_SUCCESS)
2448             continue;
2449 
2450         header = cache_container_lock_index(container);
2451         if(!header)
2452             continue;
2453 
2454         urlcache_clean_leaked_entries(container, header);
2455 
2456         desired_size = header->cache_limit.QuadPart*(100-size)/100;
2457         cur_size = header->cache_usage.QuadPart+header->exempt_usage.QuadPart;
2458         if(cur_size <= desired_size)
2459             delete_factor = 0;
2460         else
2461             delete_factor = (cur_size-desired_size)*100/cur_size;
2462 
2463         if(!delete_factor) {
2464             cache_container_unlock_index(container, header);
2465             continue;
2466         }
2467 
2468         hash_table_off = 0;
2469         hash_table_entry = 0;
2470         rate_no = 0;
2471         GetSystemTimeAsFileTime(&cur_time);
2472         while(rate_no < ARRAY_SIZE(rate) &&
2473                 urlcache_next_entry(header, &hash_table_off, &hash_table_entry, &hash_entry, &entry)) {
2474             if(entry->signature != URL_SIGNATURE) {
2475                 WARN("only url entries are currently supported\n");
2476                 continue;
2477             }
2478 
2479             url_entry = (entry_url*)entry;
2480             if(url_entry->cache_entry_type & filter)
2481                 continue;
2482 
2483             rate[rate_no] = urlcache_rate_entry(url_entry, &cur_time);
2484             if(rate[rate_no] != -1)
2485                 rate_no++;
2486         }
2487 
2488         if(!rate_no) {
2489             TRACE("nothing to delete\n");
2490             cache_container_unlock_index(container, header);
2491             continue;
2492         }
2493 
2494         qsort(rate, rate_no, sizeof(DWORD), dword_cmp);
2495 
2496         delete_factor = delete_factor*rate_no/100;
2497         delete_factor = rate[delete_factor];
2498         TRACE("deleting files with rating %d or less\n", delete_factor);
2499 
2500         hash_table_off = 0;
2501         while(urlcache_next_entry(header, &hash_table_off, &hash_table_entry, &hash_entry, &entry)) {
2502             if(entry->signature != URL_SIGNATURE)
2503                 continue;
2504 
2505             url_entry = (entry_url*)entry;
2506             if(url_entry->cache_entry_type & filter)
2507                 continue;
2508 
2509             if(urlcache_rate_entry(url_entry, &cur_time) <= delete_factor) {
2510                 TRACE("deleting file: %s\n", debugstr_a((char*)url_entry+url_entry->local_name_off));
2511                 urlcache_entry_delete(container, header, hash_entry);
2512 
2513                 if(header->cache_usage.QuadPart+header->exempt_usage.QuadPart <= desired_size)
2514                     break;
2515 
2516                 /* Allow other threads to use cache while cleaning */
2517                 cache_container_unlock_index(container, header);
2518                 if(WaitForSingleObject(dll_unload_event, 0) == WAIT_OBJECT_0) {
2519                     TRACE("got dll_unload_event - finishing\n");
2520                     return TRUE;
2521                 }
2522                 Sleep(0);
2523                 header = cache_container_lock_index(container);
2524             }
2525         }
2526 
2527         TRACE("cache size after cleaning 0x%s/0x%s\n",
2528                 wine_dbgstr_longlong(header->cache_usage.QuadPart+header->exempt_usage.QuadPart),
2529                 wine_dbgstr_longlong(header->cache_limit.QuadPart));
2530         cache_container_unlock_index(container, header);
2531     }
2532 
2533     return TRUE;
2534 }
2535 
2536 /***********************************************************************
2537  *           FreeUrlCacheSpaceA (WININET.@)
2538  *
2539  * See FreeUrlCacheSpaceW.
2540  */
2541 BOOL WINAPI FreeUrlCacheSpaceA(LPCSTR lpszCachePath, DWORD dwSize, DWORD dwFilter)
2542 {
2543     BOOL ret = FALSE;
2544     LPWSTR path = heap_strdupAtoW(lpszCachePath);
2545     if (lpszCachePath == NULL || path != NULL)
2546         ret = FreeUrlCacheSpaceW(path, dwSize, dwFilter);
2547     heap_free(path);
2548     return ret;
2549 }
2550 
2551 /***********************************************************************
2552  *           UnlockUrlCacheEntryFileA (WININET.@)
2553  *
2554  */
2555 BOOL WINAPI UnlockUrlCacheEntryFileA(LPCSTR lpszUrlName, DWORD dwReserved)
2556 {
2557     urlcache_header *pHeader;
2558     struct hash_entry *pHashEntry;
2559     entry_header *pEntry;
2560     entry_url * pUrlEntry;
2561     cache_container *pContainer;
2562     DWORD error;
2563 
2564     TRACE("(%s, 0x%08x)\n", debugstr_a(lpszUrlName), dwReserved);
2565 
2566     if (dwReserved)
2567     {
2568         ERR("dwReserved != 0\n");
2569         SetLastError(ERROR_INVALID_PARAMETER);
2570         return FALSE;
2571     }
2572 
2573     error = cache_containers_find(lpszUrlName, &pContainer);
2574     if (error != ERROR_SUCCESS)
2575     {
2576        SetLastError(error);
2577        return FALSE;
2578     }
2579 
2580     error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
2581     if (error != ERROR_SUCCESS)
2582     {
2583         SetLastError(error);
2584         return FALSE;
2585     }
2586 
2587     if (!(pHeader = cache_container_lock_index(pContainer)))
2588         return FALSE;
2589 
2590     if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
2591     {
2592         cache_container_unlock_index(pContainer, pHeader);
2593         TRACE("entry %s not found!\n", debugstr_a(lpszUrlName));
2594         SetLastError(ERROR_FILE_NOT_FOUND);
2595         return FALSE;
2596     }
2597 
2598     pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2599     if (pEntry->signature != URL_SIGNATURE)
2600     {
2601         cache_container_unlock_index(pContainer, pHeader);
2602         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->signature, sizeof(DWORD)));
2603         SetLastError(ERROR_FILE_NOT_FOUND);
2604         return FALSE;
2605     }
2606 
2607     pUrlEntry = (entry_url *)pEntry;
2608 
2609     if (pUrlEntry->use_count == 0)
2610     {
2611         cache_container_unlock_index(pContainer, pHeader);
2612         return FALSE;
2613     }
2614     pUrlEntry->use_count--;
2615     if (!pUrlEntry->use_count)
2616     {
2617         urlcache_hash_entry_set_flags(pHashEntry, HASHTABLE_URL);
2618         if (pUrlEntry->cache_entry_type & PENDING_DELETE_CACHE_ENTRY)
2619             urlcache_entry_delete(pContainer, pHeader, pHashEntry);
2620     }
2621 
2622     cache_container_unlock_index(pContainer, pHeader);
2623 
2624     return TRUE;
2625 }
2626 
2627 /***********************************************************************
2628  *           UnlockUrlCacheEntryFileW (WININET.@)
2629  *
2630  */
2631 BOOL WINAPI UnlockUrlCacheEntryFileW(LPCWSTR lpszUrlName, DWORD dwReserved)
2632 {
2633     char *url;
2634     BOOL ret;
2635 
2636     if(!urlcache_encode_url_alloc(lpszUrlName, &url))
2637         return FALSE;
2638 
2639     ret = UnlockUrlCacheEntryFileA(url, dwReserved);
2640     heap_free(url);
2641     return ret;
2642 }
2643 
2644 static BOOL urlcache_entry_create(const char *url, const char *ext, WCHAR *full_path)
2645 {
2646     cache_container *container;
2647     urlcache_header *header;
2648     char file_name[MAX_PATH];
2649     WCHAR extW[MAX_PATH];
2650     BYTE cache_dir;
2651     LONG full_path_len, ext_len = 0;
2652     BOOL generate_name = FALSE;
2653     DWORD error;
2654     HANDLE file;
2655     FILETIME ft;
2656     URL_COMPONENTSA uc;
2657     int i;
2658 
2659     TRACE("(%s, %s, %p)\n", debugstr_a(url), debugstr_a(ext), full_path);
2660 
2661     memset(&uc, 0, sizeof(uc));
2662     uc.dwStructSize = sizeof(uc);
2663     uc.dwUrlPathLength = 1;
2664     uc.dwExtraInfoLength = 1;
2665     if(!InternetCrackUrlA(url, 0, 0, &uc))
2666         uc.dwUrlPathLength = 0;
2667 
2668     if(!uc.dwUrlPathLength) {
2669         file_name[0] = 0;
2670     }else {
2671         char *p, *e;
2672 
2673         p = e = uc.lpszUrlPath+uc.dwUrlPathLength;
2674         while(p>uc.lpszUrlPath && *(p-1)!='/' && *(p-1)!='\\' && *(p-1)!='.')
2675             p--;
2676         if(p>uc.lpszUrlPath && *(p-1)=='.') {
2677             e = p-1;
2678             while(p>uc.lpszUrlPath && *(p-1)!='/' && *(p-1)!='\\')
2679                 p--;
2680         }
2681 
2682         if(e-p >= MAX_PATH)
2683             e = p+MAX_PATH-1;
2684         memcpy(file_name, p, e-p);
2685         file_name[e-p] = 0;
2686 
2687         for(p=file_name; *p; p++) {
2688             switch(*p) {
2689             case '<': case '>':
2690             case ':': case '"':
2691             case '|': case '?':
2692             case '*':
2693                 *p = '_'; break;
2694             default: break;
2695             }
2696         }
2697     }
2698 
2699     if(!file_name[0])
2700         generate_name = TRUE;
2701 
2702     error = cache_containers_find(url, &container);
2703     if(error != ERROR_SUCCESS) {
2704         SetLastError(error);
2705         return FALSE;
2706     }
2707 
2708     error = cache_container_open_index(container, MIN_BLOCK_NO);
2709     if(error != ERROR_SUCCESS) {
2710         SetLastError(error);
2711         return FALSE;
2712     }
2713 
2714     if(!(header = cache_container_lock_index(container)))
2715         return FALSE;
2716 
2717     if(header->dirs_no)
2718         cache_dir = (BYTE)(rand() % header->dirs_no);
2719     else
2720         cache_dir = CACHE_CONTAINER_NO_SUBDIR;
2721 
2722     full_path_len = MAX_PATH * sizeof(WCHAR);
2723     if(!urlcache_create_file_pathW(container, header, file_name, cache_dir, full_path, &full_path_len, TRUE)) {
2724         WARN("Failed to get full path for filename %s, needed %u bytes.\n",
2725                 debugstr_a(file_name), full_path_len);
2726         cache_container_unlock_index(container, header);
2727         return FALSE;
2728     }
2729     full_path_len = full_path_len/sizeof(WCHAR) - 1;
2730 
2731     cache_container_unlock_index(container, header);
2732 
2733     if(ext) {
2734         WCHAR *p;
2735 
2736         extW[0] = '.';
2737         ext_len = MultiByteToWideChar(CP_ACP, 0, ext, -1, extW+1, MAX_PATH-1);
2738 
2739         for(p=extW; *p; p++) {
2740             switch(*p) {
2741             case '<': case '>':
2742             case ':': case '"':
2743             case '|': case '?':
2744             case '*':
2745                 *p = '_'; break;
2746             default: break;
2747             }
2748         }
2749         if(p[-1]==' ' || p[-1]=='.')
2750             p[-1] = '_';
2751     }else {
2752         extW[0] = '\0';
2753     }
2754 
2755     if(!generate_name && full_path_len+5+ext_len>=MAX_PATH) { /* strlen("[255]") = 5 */
2756         full_path_len = MAX_PATH-5-ext_len-1;
2757     }
2758 
2759     for(i=0; i<255 && !generate_name; i++) {
2760         static const WCHAR format[] = {'[','%','u',']','%','s',0};
2761 
2762         wsprintfW(full_path+full_path_len, format, i, extW);
2763 
2764         TRACE("Trying: %s\n", debugstr_w(full_path));
2765         file = CreateFileW(full_path, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
2766         if(file != INVALID_HANDLE_VALUE) {
2767             CloseHandle(file);
2768             return TRUE;
2769         }
2770     }
2771 
2772     if(full_path_len+8+ext_len >= MAX_PATH)
2773         full_path_len = MAX_PATH-8-ext_len-1;
2774 
2775     /* Try to generate random name */
2776     GetSystemTimeAsFileTime(&ft);
2777     strcpyW(full_path+full_path_len+8, extW);
2778 
2779     for(i=0; i<255; i++) {
2780         int j;
2781         ULONGLONG n = ft.dwHighDateTime;
2782         n <<= 32;
2783         n += ft.dwLowDateTime;
2784         n ^= (ULONGLONG)i<<48;
2785 
2786         for(j=0; j<8; j++) {
2787             int r = (n % 36);
2788             n /= 37;
2789             full_path[full_path_len+j] = (r < 10 ? '0' + r : 'A' + r - 10);
2790         }
2791 
2792         TRACE("Trying: %s\n", debugstr_w(full_path));
2793         file = CreateFileW(full_path, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
2794         if(file != INVALID_HANDLE_VALUE) {
2795             CloseHandle(file);
2796             return TRUE;
2797         }
2798     }
2799 
2800     WARN("Could not find a unique filename\n");
2801     return FALSE;
2802 }
2803 
2804 /***********************************************************************
2805  *           CreateUrlCacheEntryA (WININET.@)
2806  *
2807  */
2808 BOOL WINAPI CreateUrlCacheEntryA(LPCSTR lpszUrlName, DWORD dwExpectedFileSize,
2809         LPCSTR lpszFileExtension, LPSTR lpszFileName, DWORD dwReserved)
2810 {
2811     WCHAR file_name[MAX_PATH];
2812 
2813     if(dwReserved)
2814         FIXME("dwReserved 0x%08x\n", dwReserved);
2815 
2816     if(!urlcache_entry_create(lpszUrlName, lpszFileExtension, file_name))
2817         return FALSE;
2818 
2819     if(!WideCharToMultiByte(CP_ACP, 0, file_name, -1, lpszFileName, MAX_PATH, NULL, NULL))
2820         return FALSE;
2821     return TRUE;
2822 }
2823 /***********************************************************************
2824  *           CreateUrlCacheEntryW (WININET.@)
2825  *
2826  */
2827 BOOL WINAPI CreateUrlCacheEntryW(LPCWSTR lpszUrlName, DWORD dwExpectedFileSize,
2828         LPCWSTR lpszFileExtension, LPWSTR lpszFileName, DWORD dwReserved)
2829 {
2830     char *url, *ext = NULL;
2831     BOOL ret;
2832 
2833     if(dwReserved)
2834         FIXME("dwReserved 0x%08x\n", dwReserved);
2835 
2836     if(lpszFileExtension) {
2837         ext = heap_strdupWtoUTF8(lpszFileExtension);
2838         if(!ext)
2839             return FALSE;
2840     }
2841 
2842     if(!urlcache_encode_url_alloc(lpszUrlName, &url)) {
2843         heap_free(ext);
2844         return FALSE;
2845     }
2846 
2847     ret = urlcache_entry_create(url, ext, lpszFileName);
2848     heap_free(ext);
2849     heap_free(url);
2850     return ret;
2851 }
2852 
2853 static BOOL urlcache_entry_commit(const char *url, const WCHAR *file_name,
2854     FILETIME expire_time, FILETIME modify_time, DWORD entry_type,
2855     BYTE *header_info, DWORD header_size, const char *file_ext,
2856     const char *original_url)
2857 {
2858     cache_container *container;
2859     urlcache_header *header;
2860     struct hash_entry *hash_entry;
2861     entry_header *entry;
2862     entry_url *url_entry;
2863     DWORD url_entry_offset;
2864     DWORD size = DWORD_ALIGN(sizeof(*url_entry));
2865     DWORD file_name_off = 0;
2866     DWORD header_info_off = 0;
2867     DWORD file_ext_off = 0;
2868     WIN32_FILE_ATTRIBUTE_DATA file_attr;
2869     LARGE_INTEGER file_size;
2870     BYTE dir_id;
2871     char file_name_no_container[MAX_PATH];
2872     char *local_file_name = 0;
2873     DWORD hit_rate = 0;
2874     DWORD exempt_delta = 0;
2875     DWORD error;
2876 
2877     TRACE("(%s, %s, ..., ..., %x, %p, %d, %s, %s)\n", debugstr_a(url), debugstr_w(file_name),
2878             entry_type, header_info, header_size, debugstr_a(file_ext), debugstr_a(original_url));
2879 
2880     if(entry_type & STICKY_CACHE_ENTRY && !file_name) {
2881         SetLastError(ERROR_INVALID_PARAMETER);
2882         return FALSE;
2883     }
2884     if(original_url)
2885         WARN(": original_url ignored\n");
2886 
2887     memset(&file_attr, 0, sizeof(file_attr));
2888     if(file_name) {
2889         if(!GetFileAttributesExW(file_name, GetFileExInfoStandard, &file_attr))
2890             return FALSE;
2891     }
2892     file_size.u.LowPart = file_attr.nFileSizeLow;
2893     file_size.u.HighPart = file_attr.nFileSizeHigh;
2894 
2895     error = cache_containers_find(url, &container);
2896     if(error != ERROR_SUCCESS) {
2897         SetLastError(error);
2898         return FALSE;
2899     }
2900 
2901     error = cache_container_open_index(container, MIN_BLOCK_NO);
2902     if(error != ERROR_SUCCESS) {
2903         SetLastError(error);
2904         return FALSE;
2905     }
2906 
2907     if(!(header = cache_container_lock_index(container)))
2908         return FALSE;
2909 
2910     if(urlcache_find_hash_entry(header, url, &hash_entry)) {
2911         entry_url *url_entry = (entry_url*)((LPBYTE)header + hash_entry->offset);
2912 
2913         if(urlcache_hash_entry_is_locked(hash_entry, url_entry)) {
2914             TRACE("Trying to overwrite locked entry\n");
2915             cache_container_unlock_index(container, header);
2916             SetLastError(ERROR_SHARING_VIOLATION);
2917             return FALSE;
2918         }
2919 
2920         hit_rate = url_entry->hit_rate;
2921         exempt_delta = url_entry->exempt_delta;
2922         urlcache_entry_delete(container, header, hash_entry);
2923     }
2924 
2925     if(header->dirs_no)
2926         dir_id = 0;
2927     else
2928         dir_id = CACHE_CONTAINER_NO_SUBDIR;
2929 
2930     if(file_name) {
2931         BOOL bFound = FALSE;
2932 
2933         if(strncmpW(file_name, container->path, lstrlenW(container->path))) {
2934             ERR("path %s must begin with cache content path %s\n", debugstr_w(file_name), debugstr_w(container->path));
2935             cache_container_unlock_index(container, header);
2936             SetLastError(ERROR_INVALID_PARAMETER);
2937             return FALSE;
2938         }
2939 
2940         /* skip container path prefix */
2941         file_name += lstrlenW(container->path);
2942 
2943         WideCharToMultiByte(CP_ACP, 0, file_name, -1, file_name_no_container, MAX_PATH, NULL, NULL);
2944 	local_file_name = file_name_no_container;
2945 
2946         if(header->dirs_no) {
2947             for(dir_id = 0; dir_id < header->dirs_no; dir_id++) {
2948                 if(!strncmp(header->directory_data[dir_id].name, local_file_name, DIR_LENGTH)) {
2949                     bFound = TRUE;
2950                     break;
2951                 }
2952             }
2953 
2954             if(!bFound) {
2955                 ERR("cache directory not found in path %s\n", debugstr_w(file_name));
2956                 cache_container_unlock_index(container, header);
2957                 SetLastError(ERROR_INVALID_PARAMETER);
2958                 return FALSE;
2959             }
2960 
2961             file_name += DIR_LENGTH + 1;
2962             local_file_name += DIR_LENGTH + 1;
2963         }
2964     }
2965 
2966     size = DWORD_ALIGN(size + strlen(url) + 1);
2967     if(file_name) {
2968         file_name_off = size;
2969         size = DWORD_ALIGN(size + strlen(local_file_name) + 1);
2970     }
2971     if(header_info && header_size) {
2972         header_info_off = size;
2973         size = DWORD_ALIGN(size + header_size);
2974     }
2975     if(file_ext && (file_ext_off = strlen(file_ext))) {
2976         DWORD len = file_ext_off;
2977 
2978         file_ext_off = size;
2979         size = DWORD_ALIGN(size + len + 1);
2980     }
2981 
2982     /* round up to next block */
2983     if(size % BLOCKSIZE) {
2984         size -= size % BLOCKSIZE;
2985         size += BLOCKSIZE;
2986     }
2987 
2988     error = urlcache_entry_alloc(header, size / BLOCKSIZE, &entry);
2989     while(error == ERROR_HANDLE_DISK_FULL) {
2990         error = cache_container_clean_index(container, &header);
2991         if(error == ERROR_SUCCESS)
2992             error = urlcache_entry_alloc(header, size / BLOCKSIZE, &entry);
2993     }
2994     if(error != ERROR_SUCCESS) {
2995         cache_container_unlock_index(container, header);
2996         SetLastError(error);
2997         return FALSE;
2998     }
2999 
3000     /* FindFirstFreeEntry fills in blocks used */
3001     url_entry = (entry_url *)entry;
3002     url_entry_offset = (LPBYTE)url_entry - (LPBYTE)header;
3003     url_entry->header.signature = URL_SIGNATURE;
3004     url_entry->cache_dir = dir_id;
3005     url_entry->cache_entry_type = entry_type | container->default_entry_type;
3006     url_entry->header_info_size = header_size;
3007     if((entry_type & STICKY_CACHE_ENTRY) && !exempt_delta) {
3008         /* Sticky entries have a default exempt time of one day */
3009         exempt_delta = 86400;
3010     }
3011     url_entry->exempt_delta = exempt_delta;
3012     url_entry->hit_rate = hit_rate+1;
3013     url_entry->file_extension_off = file_ext_off;
3014     url_entry->header_info_off = header_info_off;
3015     url_entry->local_name_off = file_name_off;
3016     url_entry->url_off = DWORD_ALIGN(sizeof(*url_entry));
3017     url_entry->size.QuadPart = file_size.QuadPart;
3018     url_entry->use_count = 0;
3019     GetSystemTimeAsFileTime(&url_entry->access_time);
3020     url_entry->modification_time = modify_time;
3021     file_time_to_dos_date_time(&url_entry->access_time, &url_entry->sync_date, &url_entry->sync_time);
3022     file_time_to_dos_date_time(&expire_time, &url_entry->expire_date, &url_entry->expire_time);
3023     file_time_to_dos_date_time(&file_attr.ftLastWriteTime, &url_entry->write_date, &url_entry->write_time);
3024 
3025     /*** Unknowns ***/
3026     url_entry->unk1 = 0;
3027     url_entry->unk2 = 0;
3028     url_entry->unk3 = 0x60;
3029     url_entry->unk4 = 0;
3030     url_entry->unk5 = 0x1010;
3031     url_entry->unk7 = 0;
3032     url_entry->unk8 = 0;
3033 
3034 
3035     strcpy((LPSTR)url_entry + url_entry->url_off, url);
3036     if(file_name_off)
3037         strcpy((LPSTR)((LPBYTE)url_entry + file_name_off), local_file_name);
3038     if(header_info_off)
3039         memcpy((LPBYTE)url_entry + header_info_off, header_info, header_size);
3040     if(file_ext_off)
3041         strcpy((LPSTR)((LPBYTE)url_entry + file_ext_off), file_ext);
3042 
3043     error = urlcache_hash_entry_create(header, url, url_entry_offset, HASHTABLE_URL);
3044     while(error == ERROR_HANDLE_DISK_FULL) {
3045         error = cache_container_clean_index(container, &header);
3046         if(error == ERROR_SUCCESS) {
3047             url_entry = (entry_url *)((LPBYTE)header + url_entry_offset);
3048             error = urlcache_hash_entry_create(header, url,
3049                     url_entry_offset, HASHTABLE_URL);
3050         }
3051     }
3052     if(error != ERROR_SUCCESS) {
3053         urlcache_entry_free(header, &url_entry->header);
3054         cache_container_unlock_index(container, header);
3055         SetLastError(error);
3056         return FALSE;
3057     }
3058 
3059     if(url_entry->cache_dir < header->dirs_no)
3060         header->directory_data[url_entry->cache_dir].files_no++;
3061     if(entry_type & STICKY_CACHE_ENTRY)
3062         header->exempt_usage.QuadPart += file_size.QuadPart;
3063     else
3064         header->cache_usage.QuadPart += file_size.QuadPart;
3065     if(header->cache_usage.QuadPart+header->exempt_usage.QuadPart > header->cache_limit.QuadPart)
3066             handle_full_cache();
3067 
3068     cache_container_unlock_index(container, header);
3069     return TRUE;
3070 }
3071 
3072 /***********************************************************************
3073  *           CommitUrlCacheEntryA (WININET.@)
3074  */
3075 BOOL WINAPI CommitUrlCacheEntryA(LPCSTR lpszUrlName, LPCSTR lpszLocalFileName,
3076         FILETIME ExpireTime, FILETIME LastModifiedTime, DWORD CacheEntryType,
3077         LPBYTE lpHeaderInfo, DWORD dwHeaderSize, LPCSTR lpszFileExtension, LPCSTR lpszOriginalUrl)
3078 {
3079     WCHAR *file_name = NULL;
3080     BOOL ret;
3081 
3082     if(lpszLocalFileName) {
3083         file_name = heap_strdupAtoW(lpszLocalFileName);
3084         if(!file_name)
3085             return FALSE;
3086     }
3087 
3088     ret = urlcache_entry_commit(lpszUrlName, file_name, ExpireTime, LastModifiedTime,
3089             CacheEntryType, lpHeaderInfo, dwHeaderSize, lpszFileExtension, lpszOriginalUrl);
3090     heap_free(file_name);
3091     return ret;
3092 }
3093 
3094 /***********************************************************************
3095  *           CommitUrlCacheEntryW (WININET.@)
3096  */
3097 BOOL WINAPI CommitUrlCacheEntryW(LPCWSTR lpszUrlName, LPCWSTR lpszLocalFileName,
3098         FILETIME ExpireTime, FILETIME LastModifiedTime, DWORD CacheEntryType,
3099         LPWSTR lpHeaderInfo, DWORD dwHeaderSize, LPCWSTR lpszFileExtension, LPCWSTR lpszOriginalUrl)
3100 {
3101     char *url, *original_url=NULL, *file_ext=NULL, *header_info=NULL;
3102     BOOL ret;
3103 
3104     if(!urlcache_encode_url_alloc(lpszUrlName, &url))
3105         return FALSE;
3106 
3107     if(lpHeaderInfo) {
3108         header_info = heap_strdupWtoUTF8(lpHeaderInfo);
3109         if(!header_info) {
3110             heap_free(url);
3111             return FALSE;
3112         }
3113         dwHeaderSize = strlen(header_info);
3114     }
3115 
3116     if(lpszFileExtension) {
3117         file_ext = heap_strdupWtoA(lpszFileExtension);
3118         if(!file_ext) {
3119             heap_free(url);
3120             heap_free(header_info);
3121             return FALSE;
3122         }
3123     }
3124 
3125     if(lpszOriginalUrl && !urlcache_encode_url_alloc(lpszOriginalUrl, &original_url)) {
3126         heap_free(url);
3127         heap_free(header_info);
3128         heap_free(file_ext);
3129         return FALSE;
3130     }
3131 
3132     ret = urlcache_entry_commit(url, lpszLocalFileName, ExpireTime, LastModifiedTime,
3133             CacheEntryType, (BYTE*)header_info, dwHeaderSize, file_ext, original_url);
3134     heap_free(url);
3135     heap_free(header_info);
3136     heap_free(file_ext);
3137     heap_free(original_url);
3138     return ret;
3139 }
3140 
3141 /***********************************************************************
3142  *           ReadUrlCacheEntryStream (WININET.@)
3143  *
3144  */
3145 BOOL WINAPI ReadUrlCacheEntryStream(
3146     IN HANDLE hUrlCacheStream,
3147     IN  DWORD dwLocation,
3148     IN OUT LPVOID lpBuffer,
3149     IN OUT LPDWORD lpdwLen,
3150     IN DWORD dwReserved
3151     )
3152 {
3153     /* Get handle to file from 'stream' */
3154     stream_handle *pStream = (stream_handle*)hUrlCacheStream;
3155 
3156     if (dwReserved != 0)
3157     {
3158         ERR("dwReserved != 0\n");
3159         SetLastError(ERROR_INVALID_PARAMETER);
3160         return FALSE;
3161     }
3162 
3163     if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->url, INTERNET_MAX_URL_LENGTH))
3164     {
3165         SetLastError(ERROR_INVALID_HANDLE);
3166         return FALSE;
3167     }
3168 
3169     if (SetFilePointer(pStream->file, dwLocation, NULL, FILE_CURRENT) == INVALID_SET_FILE_POINTER)
3170         return FALSE;
3171     return ReadFile(pStream->file, lpBuffer, *lpdwLen, lpdwLen, NULL);
3172 }
3173 
3174 /***********************************************************************
3175  *           RetrieveUrlCacheEntryStreamA (WININET.@)
3176  *
3177  */
3178 HANDLE WINAPI RetrieveUrlCacheEntryStreamA(LPCSTR lpszUrlName,
3179         LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
3180         LPDWORD lpdwCacheEntryInfoBufferSize, BOOL fRandomRead, DWORD dwReserved)
3181 {
3182     /* NOTE: this is not the same as the way that the native
3183      * version allocates 'stream' handles. I did it this way
3184      * as it is much easier and no applications should depend
3185      * on this behaviour. (Native version appears to allocate
3186      * indices into a table)
3187      */
3188     stream_handle *stream;
3189     HANDLE file;
3190 
3191     TRACE("(%s, %p, %p, %x, 0x%08x)\n", debugstr_a(lpszUrlName), lpCacheEntryInfo,
3192             lpdwCacheEntryInfoBufferSize, fRandomRead, dwReserved);
3193 
3194     if(!RetrieveUrlCacheEntryFileA(lpszUrlName, lpCacheEntryInfo,
3195                 lpdwCacheEntryInfoBufferSize, dwReserved))
3196         return NULL;
3197 
3198     file = CreateFileA(lpCacheEntryInfo->lpszLocalFileName, GENERIC_READ, FILE_SHARE_READ,
3199             NULL, OPEN_EXISTING, fRandomRead ? FILE_FLAG_RANDOM_ACCESS : 0, NULL);
3200     if(file == INVALID_HANDLE_VALUE) {
3201         UnlockUrlCacheEntryFileA(lpszUrlName, 0);
3202         return NULL;
3203     }
3204 
3205     /* allocate handle storage space */
3206     stream = heap_alloc(sizeof(stream_handle) + strlen(lpszUrlName) * sizeof(CHAR));
3207     if(!stream) {
3208         CloseHandle(file);
3209         UnlockUrlCacheEntryFileA(lpszUrlName, 0);
3210         SetLastError(ERROR_OUTOFMEMORY);
3211         return NULL;
3212     }
3213 
3214     stream->file = file;
3215     strcpy(stream->url, lpszUrlName);
3216     return stream;
3217 }
3218 
3219 /***********************************************************************
3220  *           RetrieveUrlCacheEntryStreamW (WININET.@)
3221  *
3222  */
3223 HANDLE WINAPI RetrieveUrlCacheEntryStreamW(LPCWSTR lpszUrlName,
3224         LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
3225         LPDWORD lpdwCacheEntryInfoBufferSize, BOOL fRandomRead, DWORD dwReserved)
3226 {
3227     DWORD len;
3228     /* NOTE: this is not the same as the way that the native
3229      * version allocates 'stream' handles. I did it this way
3230      * as it is much easier and no applications should depend
3231      * on this behaviour. (Native version appears to allocate
3232      * indices into a table)
3233      */
3234     stream_handle *stream;
3235     HANDLE file;
3236 
3237     TRACE("(%s, %p, %p, %x, 0x%08x)\n", debugstr_w(lpszUrlName), lpCacheEntryInfo,
3238             lpdwCacheEntryInfoBufferSize, fRandomRead, dwReserved);
3239 
3240     if(!(len = urlcache_encode_url(lpszUrlName, NULL, 0)))
3241         return NULL;
3242 
3243     if(!RetrieveUrlCacheEntryFileW(lpszUrlName, lpCacheEntryInfo,
3244                 lpdwCacheEntryInfoBufferSize, dwReserved))
3245         return NULL;
3246 
3247     file = CreateFileW(lpCacheEntryInfo->lpszLocalFileName, GENERIC_READ, FILE_SHARE_READ,
3248             NULL, OPEN_EXISTING, fRandomRead ? FILE_FLAG_RANDOM_ACCESS : 0, NULL);
3249     if(file == INVALID_HANDLE_VALUE) {
3250         UnlockUrlCacheEntryFileW(lpszUrlName, 0);
3251         return NULL;
3252     }
3253 
3254     /* allocate handle storage space */
3255     stream = heap_alloc(sizeof(stream_handle) + len*sizeof(WCHAR));
3256     if(!stream) {
3257         CloseHandle(file);
3258         UnlockUrlCacheEntryFileW(lpszUrlName, 0);
3259         SetLastError(ERROR_OUTOFMEMORY);
3260         return NULL;
3261     }
3262 
3263     stream->file = file;
3264     if(!urlcache_encode_url(lpszUrlName, stream->url, len)) {
3265         CloseHandle(file);
3266         UnlockUrlCacheEntryFileW(lpszUrlName, 0);
3267         heap_free(stream);
3268         return NULL;
3269     }
3270     return stream;
3271 }
3272 
3273 /***********************************************************************
3274  *           UnlockUrlCacheEntryStream (WININET.@)
3275  *
3276  */
3277 BOOL WINAPI UnlockUrlCacheEntryStream(
3278     IN HANDLE hUrlCacheStream,
3279     IN DWORD dwReserved
3280 )
3281 {
3282     stream_handle *pStream = (stream_handle*)hUrlCacheStream;
3283 
3284     if (dwReserved != 0)
3285     {
3286         ERR("dwReserved != 0\n");
3287         SetLastError(ERROR_INVALID_PARAMETER);
3288         return FALSE;
3289     }
3290 
3291     if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->url, INTERNET_MAX_URL_LENGTH))
3292     {
3293         SetLastError(ERROR_INVALID_HANDLE);
3294         return FALSE;
3295     }
3296 
3297     if (!UnlockUrlCacheEntryFileA(pStream->url, 0))
3298         return FALSE;
3299 
3300     CloseHandle(pStream->file);
3301     heap_free(pStream);
3302     return TRUE;
3303 }
3304 
3305 
3306 /***********************************************************************
3307  *           DeleteUrlCacheEntryA (WININET.@)
3308  *
3309  */
3310 BOOL WINAPI DeleteUrlCacheEntryA(LPCSTR lpszUrlName)
3311 {
3312     cache_container *pContainer;
3313     urlcache_header *pHeader;
3314     struct hash_entry *pHashEntry;
3315     DWORD error;
3316     BOOL ret;
3317 
3318     TRACE("(%s)\n", debugstr_a(lpszUrlName));
3319 
3320     error = cache_containers_find(lpszUrlName, &pContainer);
3321     if (error != ERROR_SUCCESS)
3322     {
3323         SetLastError(error);
3324         return FALSE;
3325     }
3326 
3327     error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
3328     if (error != ERROR_SUCCESS)
3329     {
3330         SetLastError(error);
3331         return FALSE;
3332     }
3333 
3334     if (!(pHeader = cache_container_lock_index(pContainer)))
3335         return FALSE;
3336 
3337     if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
3338     {
3339         cache_container_unlock_index(pContainer, pHeader);
3340         TRACE("entry %s not found!\n", debugstr_a(lpszUrlName));
3341         SetLastError(ERROR_FILE_NOT_FOUND);
3342         return FALSE;
3343     }
3344 
3345     ret = urlcache_entry_delete(pContainer, pHeader, pHashEntry);
3346 
3347     cache_container_unlock_index(pContainer, pHeader);
3348 
3349     return ret;
3350 }
3351 
3352 /***********************************************************************
3353  *           DeleteUrlCacheEntryW (WININET.@)
3354  *
3355  */
3356 BOOL WINAPI DeleteUrlCacheEntryW(LPCWSTR lpszUrlName)
3357 {
3358     char *url;
3359     BOOL ret;
3360 
3361     if(!urlcache_encode_url_alloc(lpszUrlName, &url))
3362         return FALSE;
3363 
3364     ret = DeleteUrlCacheEntryA(url);
3365     heap_free(url);
3366     return ret;
3367 }
3368 
3369 BOOL WINAPI DeleteUrlCacheContainerA(DWORD d1, DWORD d2)
3370 {
3371     FIXME("(0x%08x, 0x%08x) stub\n", d1, d2);
3372     return TRUE;
3373 }
3374 
3375 BOOL WINAPI DeleteUrlCacheContainerW(DWORD d1, DWORD d2)
3376 {
3377     FIXME("(0x%08x, 0x%08x) stub\n", d1, d2);
3378     return TRUE;
3379 }
3380 
3381 /***********************************************************************
3382  *           CreateCacheContainerA (WININET.@)
3383  */
3384 BOOL WINAPI CreateUrlCacheContainerA(DWORD d1, DWORD d2, DWORD d3, DWORD d4,
3385                                      DWORD d5, DWORD d6, DWORD d7, DWORD d8)
3386 {
3387     FIXME("(0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x) stub\n",
3388           d1, d2, d3, d4, d5, d6, d7, d8);
3389     return TRUE;
3390 }
3391 
3392 /***********************************************************************
3393  *           CreateCacheContainerW (WININET.@)
3394  */
3395 BOOL WINAPI CreateUrlCacheContainerW(DWORD d1, DWORD d2, DWORD d3, DWORD d4,
3396                                      DWORD d5, DWORD d6, DWORD d7, DWORD d8)
3397 {
3398     FIXME("(0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x) stub\n",
3399           d1, d2, d3, d4, d5, d6, d7, d8);
3400     return TRUE;
3401 }
3402 
3403 /***********************************************************************
3404  *           FindFirstUrlCacheContainerA (WININET.@)
3405  */
3406 HANDLE WINAPI FindFirstUrlCacheContainerA( LPVOID p1, LPVOID p2, LPVOID p3, DWORD d1 )
3407 {
3408     FIXME("(%p, %p, %p, 0x%08x) stub\n", p1, p2, p3, d1 );
3409     return NULL;
3410 }
3411 
3412 /***********************************************************************
3413  *           FindFirstUrlCacheContainerW (WININET.@)
3414  */
3415 HANDLE WINAPI FindFirstUrlCacheContainerW( LPVOID p1, LPVOID p2, LPVOID p3, DWORD d1 )
3416 {
3417     FIXME("(%p, %p, %p, 0x%08x) stub\n", p1, p2, p3, d1 );
3418     return NULL;
3419 }
3420 
3421 /***********************************************************************
3422  *           FindNextUrlCacheContainerA (WININET.@)
3423  */
3424 BOOL WINAPI FindNextUrlCacheContainerA( HANDLE handle, LPVOID p1, LPVOID p2 )
3425 {
3426     FIXME("(%p, %p, %p) stub\n", handle, p1, p2 );
3427     return FALSE;
3428 }
3429 
3430 /***********************************************************************
3431  *           FindNextUrlCacheContainerW (WININET.@)
3432  */
3433 BOOL WINAPI FindNextUrlCacheContainerW( HANDLE handle, LPVOID p1, LPVOID p2 )
3434 {
3435     FIXME("(%p, %p, %p) stub\n", handle, p1, p2 );
3436     return FALSE;
3437 }
3438 
3439 HANDLE WINAPI FindFirstUrlCacheEntryExA(
3440   LPCSTR lpszUrlSearchPattern,
3441   DWORD dwFlags,
3442   DWORD dwFilter,
3443   GROUPID GroupId,
3444   LPINTERNET_CACHE_ENTRY_INFOA lpFirstCacheEntryInfo,
3445   LPDWORD lpdwFirstCacheEntryInfoBufferSize,
3446   LPVOID lpReserved,
3447   LPDWORD pcbReserved2,
3448   LPVOID lpReserved3
3449 )
3450 {
3451     FIXME("(%s, 0x%08x, 0x%08x, 0x%s, %p, %p, %p, %p, %p) stub\n", debugstr_a(lpszUrlSearchPattern),
3452           dwFlags, dwFilter, wine_dbgstr_longlong(GroupId), lpFirstCacheEntryInfo,
3453           lpdwFirstCacheEntryInfoBufferSize, lpReserved, pcbReserved2,lpReserved3);
3454     SetLastError(ERROR_FILE_NOT_FOUND);
3455     return NULL;
3456 }
3457 
3458 HANDLE WINAPI FindFirstUrlCacheEntryExW(
3459   LPCWSTR lpszUrlSearchPattern,
3460   DWORD dwFlags,
3461   DWORD dwFilter,
3462   GROUPID GroupId,
3463   LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo,
3464   LPDWORD lpdwFirstCacheEntryInfoBufferSize,
3465   LPVOID lpReserved,
3466   LPDWORD pcbReserved2,
3467   LPVOID lpReserved3
3468 )
3469 {
3470     FIXME("(%s, 0x%08x, 0x%08x, 0x%s, %p, %p, %p, %p, %p) stub\n", debugstr_w(lpszUrlSearchPattern),
3471           dwFlags, dwFilter, wine_dbgstr_longlong(GroupId), lpFirstCacheEntryInfo,
3472           lpdwFirstCacheEntryInfoBufferSize, lpReserved, pcbReserved2,lpReserved3);
3473     SetLastError(ERROR_FILE_NOT_FOUND);
3474     return NULL;
3475 }
3476 
3477 /***********************************************************************
3478  *           FindFirstUrlCacheEntryA (WININET.@)
3479  *
3480  */
3481 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryA(LPCSTR lpszUrlSearchPattern,
3482  LPINTERNET_CACHE_ENTRY_INFOA lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
3483 {
3484     find_handle *pEntryHandle;
3485 
3486     TRACE("(%s, %p, %p)\n", debugstr_a(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
3487 
3488     pEntryHandle = heap_alloc(sizeof(*pEntryHandle));
3489     if (!pEntryHandle)
3490         return NULL;
3491 
3492     pEntryHandle->magic = URLCACHE_FIND_ENTRY_HANDLE_MAGIC;
3493     if (lpszUrlSearchPattern)
3494     {
3495         pEntryHandle->url_search_pattern = heap_strdupA(lpszUrlSearchPattern);
3496         if (!pEntryHandle->url_search_pattern)
3497         {
3498             heap_free(pEntryHandle);
3499             return NULL;
3500         }
3501     }
3502     else
3503         pEntryHandle->url_search_pattern = NULL;
3504     pEntryHandle->container_idx = 0;
3505     pEntryHandle->hash_table_idx = 0;
3506     pEntryHandle->hash_entry_idx = 0;
3507 
3508     if (!FindNextUrlCacheEntryA(pEntryHandle, lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize))
3509     {
3510         heap_free(pEntryHandle);
3511         return NULL;
3512     }
3513     return pEntryHandle;
3514 }
3515 
3516 /***********************************************************************
3517  *           FindFirstUrlCacheEntryW (WININET.@)
3518  *
3519  */
3520 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryW(LPCWSTR lpszUrlSearchPattern,
3521  LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
3522 {
3523     find_handle *pEntryHandle;
3524 
3525     TRACE("(%s, %p, %p)\n", debugstr_w(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
3526 
3527     pEntryHandle = heap_alloc(sizeof(*pEntryHandle));
3528     if (!pEntryHandle)
3529         return NULL;
3530 
3531     pEntryHandle->magic = URLCACHE_FIND_ENTRY_HANDLE_MAGIC;
3532     if (lpszUrlSearchPattern)
3533     {
3534         pEntryHandle->url_search_pattern = heap_strdupWtoA(lpszUrlSearchPattern);
3535         if (!pEntryHandle->url_search_pattern)
3536         {
3537             heap_free(pEntryHandle);
3538             return NULL;
3539         }
3540     }
3541     else
3542         pEntryHandle->url_search_pattern = NULL;
3543     pEntryHandle->container_idx = 0;
3544     pEntryHandle->hash_table_idx = 0;
3545     pEntryHandle->hash_entry_idx = 0;
3546 
3547     if (!FindNextUrlCacheEntryW(pEntryHandle, lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize))
3548     {
3549         heap_free(pEntryHandle);
3550         return NULL;
3551     }
3552     return pEntryHandle;
3553 }
3554 
3555 static BOOL urlcache_find_next_entry(
3556   HANDLE hEnumHandle,
3557   LPINTERNET_CACHE_ENTRY_INFOA lpNextCacheEntryInfo,
3558   LPDWORD lpdwNextCacheEntryInfoBufferSize,
3559   BOOL unicode)
3560 {
3561     find_handle *pEntryHandle = (find_handle*)hEnumHandle;
3562     cache_container *pContainer;
3563 
3564     if (pEntryHandle->magic != URLCACHE_FIND_ENTRY_HANDLE_MAGIC)
3565     {
3566         SetLastError(ERROR_INVALID_HANDLE);
3567         return FALSE;
3568     }
3569 
3570     for (; cache_containers_enum(pEntryHandle->url_search_pattern, pEntryHandle->container_idx, &pContainer);
3571          pEntryHandle->container_idx++, pEntryHandle->hash_table_idx = 0)
3572     {
3573         urlcache_header *pHeader;
3574         entry_hash_table *pHashTableEntry;
3575         DWORD error;
3576 
3577         error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
3578         if (error != ERROR_SUCCESS)
3579         {
3580             SetLastError(error);
3581             return FALSE;
3582         }
3583 
3584         if (!(pHeader = cache_container_lock_index(pContainer)))
3585             return FALSE;
3586 
3587         for (; urlcache_enum_hash_tables(pHeader, &pEntryHandle->hash_table_idx, &pHashTableEntry);
3588              pEntryHandle->hash_table_idx++, pEntryHandle->hash_entry_idx = 0)
3589         {
3590             const struct hash_entry *pHashEntry = NULL;
3591             for (; urlcache_enum_hash_table_entries(pHeader, pHashTableEntry, &pEntryHandle->hash_entry_idx, &pHashEntry);
3592                  pEntryHandle->hash_entry_idx++)
3593             {
3594                 const entry_url *pUrlEntry;
3595                 const entry_header *pEntry = (const entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
3596 
3597                 if (pEntry->signature != URL_SIGNATURE)
3598                     continue;
3599 
3600                 pUrlEntry = (const entry_url *)pEntry;
3601                 TRACE("Found URL: %s\n",
3602                       debugstr_a((LPCSTR)pUrlEntry + pUrlEntry->url_off));
3603                 TRACE("Header info: %s\n",
3604                         debugstr_an((LPCSTR)pUrlEntry + pUrlEntry->header_info_off,
3605                             pUrlEntry->header_info_size));
3606 
3607                 error = urlcache_copy_entry(
3608                     pContainer,
3609                     pHeader,
3610                     lpNextCacheEntryInfo,
3611                     lpdwNextCacheEntryInfoBufferSize,
3612                     pUrlEntry,
3613                     unicode);
3614                 if (error != ERROR_SUCCESS)
3615                 {
3616                     cache_container_unlock_index(pContainer, pHeader);
3617                     SetLastError(error);
3618                     return FALSE;
3619                 }
3620                 if(pUrlEntry->local_name_off)
3621                     TRACE("Local File Name: %s\n", debugstr_a((LPCSTR)pUrlEntry + pUrlEntry->local_name_off));
3622 
3623                 /* increment the current index so that next time the function
3624                  * is called the next entry is returned */
3625                 pEntryHandle->hash_entry_idx++;
3626                 cache_container_unlock_index(pContainer, pHeader);
3627                 return TRUE;
3628             }
3629         }
3630 
3631         cache_container_unlock_index(pContainer, pHeader);
3632     }
3633 
3634     SetLastError(ERROR_NO_MORE_ITEMS);
3635     return FALSE;
3636 }
3637 
3638 /***********************************************************************
3639  *           FindNextUrlCacheEntryA (WININET.@)
3640  */
3641 BOOL WINAPI FindNextUrlCacheEntryA(
3642   HANDLE hEnumHandle,
3643   LPINTERNET_CACHE_ENTRY_INFOA lpNextCacheEntryInfo,
3644   LPDWORD lpdwNextCacheEntryInfoBufferSize)
3645 {
3646     TRACE("(%p, %p, %p)\n", hEnumHandle, lpNextCacheEntryInfo, lpdwNextCacheEntryInfoBufferSize);
3647 
3648     return urlcache_find_next_entry(hEnumHandle, lpNextCacheEntryInfo,
3649             lpdwNextCacheEntryInfoBufferSize, FALSE /* not UNICODE */);
3650 }
3651 
3652 /***********************************************************************
3653  *           FindNextUrlCacheEntryW (WININET.@)
3654  */
3655 BOOL WINAPI FindNextUrlCacheEntryW(
3656   HANDLE hEnumHandle,
3657   LPINTERNET_CACHE_ENTRY_INFOW lpNextCacheEntryInfo,
3658   LPDWORD lpdwNextCacheEntryInfoBufferSize
3659 )
3660 {
3661     TRACE("(%p, %p, %p)\n", hEnumHandle, lpNextCacheEntryInfo, lpdwNextCacheEntryInfoBufferSize);
3662 
3663     return urlcache_find_next_entry(hEnumHandle,
3664             (LPINTERNET_CACHE_ENTRY_INFOA)lpNextCacheEntryInfo,
3665             lpdwNextCacheEntryInfoBufferSize, TRUE /* UNICODE */);
3666 }
3667 
3668 /***********************************************************************
3669  *           FindCloseUrlCache (WININET.@)
3670  */
3671 BOOL WINAPI FindCloseUrlCache(HANDLE hEnumHandle)
3672 {
3673     find_handle *pEntryHandle = (find_handle*)hEnumHandle;
3674 
3675     TRACE("(%p)\n", hEnumHandle);
3676 
3677     if (!pEntryHandle || pEntryHandle->magic != URLCACHE_FIND_ENTRY_HANDLE_MAGIC)
3678     {
3679         SetLastError(ERROR_INVALID_HANDLE);
3680         return FALSE;
3681     }
3682 
3683     pEntryHandle->magic = 0;
3684     heap_free(pEntryHandle->url_search_pattern);
3685     heap_free(pEntryHandle);
3686     return TRUE;
3687 }
3688 
3689 HANDLE WINAPI FindFirstUrlCacheGroup( DWORD dwFlags, DWORD dwFilter, LPVOID lpSearchCondition,
3690                                       DWORD dwSearchCondition, GROUPID* lpGroupId, LPVOID lpReserved )
3691 {
3692     FIXME("(0x%08x, 0x%08x, %p, 0x%08x, %p, %p) stub\n", dwFlags, dwFilter, lpSearchCondition,
3693           dwSearchCondition, lpGroupId, lpReserved);
3694     return NULL;
3695 }
3696 
3697 BOOL WINAPI FindNextUrlCacheEntryExA(
3698   HANDLE hEnumHandle,
3699   LPINTERNET_CACHE_ENTRY_INFOA lpFirstCacheEntryInfo,
3700   LPDWORD lpdwFirstCacheEntryInfoBufferSize,
3701   LPVOID lpReserved,
3702   LPDWORD pcbReserved2,
3703   LPVOID lpReserved3
3704 )
3705 {
3706     FIXME("(%p, %p, %p, %p, %p, %p) stub\n", hEnumHandle, lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize,
3707           lpReserved, pcbReserved2, lpReserved3);
3708     return FALSE;
3709 }
3710 
3711 BOOL WINAPI FindNextUrlCacheEntryExW(
3712   HANDLE hEnumHandle,
3713   LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo,
3714   LPDWORD lpdwFirstCacheEntryInfoBufferSize,
3715   LPVOID lpReserved,
3716   LPDWORD pcbReserved2,
3717   LPVOID lpReserved3
3718 )
3719 {
3720     FIXME("(%p, %p, %p, %p, %p, %p) stub\n", hEnumHandle, lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize,
3721           lpReserved, pcbReserved2, lpReserved3);
3722     return FALSE;
3723 }
3724 
3725 BOOL WINAPI FindNextUrlCacheGroup( HANDLE hFind, GROUPID* lpGroupId, LPVOID lpReserved )
3726 {
3727     FIXME("(%p, %p, %p) stub\n", hFind, lpGroupId, lpReserved);
3728     return FALSE;
3729 }
3730 
3731 /***********************************************************************
3732  *           CreateUrlCacheGroup (WININET.@)
3733  *
3734  */
3735 INTERNETAPI GROUPID WINAPI CreateUrlCacheGroup(DWORD dwFlags, LPVOID lpReserved)
3736 {
3737   FIXME("(0x%08x, %p): stub\n", dwFlags, lpReserved);
3738   return FALSE;
3739 }
3740 
3741 /***********************************************************************
3742  *           DeleteUrlCacheGroup (WININET.@)
3743  *
3744  */
3745 BOOL WINAPI DeleteUrlCacheGroup(GROUPID GroupId, DWORD dwFlags, LPVOID lpReserved)
3746 {
3747     FIXME("(0x%s, 0x%08x, %p) stub\n",
3748           wine_dbgstr_longlong(GroupId), dwFlags, lpReserved);
3749     return FALSE;
3750 }
3751 
3752 /***********************************************************************
3753  *           DeleteWpadCacheForNetworks (WININET.@)
3754  *    Undocumented, added in IE8
3755  */
3756 BOOL WINAPI DeleteWpadCacheForNetworks(DWORD unk1)
3757 {
3758     FIXME("(%d) stub\n", unk1);
3759     return FALSE;
3760 }
3761 
3762 /***********************************************************************
3763  *           SetUrlCacheEntryGroupA (WININET.@)
3764  *
3765  */
3766 BOOL WINAPI SetUrlCacheEntryGroupA(LPCSTR lpszUrlName, DWORD dwFlags,
3767   GROUPID GroupId, LPBYTE pbGroupAttributes, DWORD cbGroupAttributes,
3768   LPVOID lpReserved)
3769 {
3770     FIXME("(%s, 0x%08x, 0x%s, %p, 0x%08x, %p) stub\n",
3771           debugstr_a(lpszUrlName), dwFlags, wine_dbgstr_longlong(GroupId),
3772           pbGroupAttributes, cbGroupAttributes, lpReserved);
3773     SetLastError(ERROR_FILE_NOT_FOUND);
3774     return FALSE;
3775 }
3776 
3777 /***********************************************************************
3778  *           SetUrlCacheEntryGroupW (WININET.@)
3779  *
3780  */
3781 BOOL WINAPI SetUrlCacheEntryGroupW(LPCWSTR lpszUrlName, DWORD dwFlags,
3782   GROUPID GroupId, LPBYTE pbGroupAttributes, DWORD cbGroupAttributes,
3783   LPVOID lpReserved)
3784 {
3785     FIXME("(%s, 0x%08x, 0x%s, %p, 0x%08x, %p) stub\n",
3786           debugstr_w(lpszUrlName), dwFlags, wine_dbgstr_longlong(GroupId),
3787           pbGroupAttributes, cbGroupAttributes, lpReserved);
3788     SetLastError(ERROR_FILE_NOT_FOUND);
3789     return FALSE;
3790 }
3791 
3792 static cache_container *find_container(DWORD flags)
3793 {
3794     cache_container *container;
3795 
3796     LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
3797     {
3798         switch (flags & (CACHE_CONFIG_CONTENT_PATHS_FC | CACHE_CONFIG_COOKIES_PATHS_FC | CACHE_CONFIG_HISTORY_PATHS_FC))
3799         {
3800         case 0:
3801         case CACHE_CONFIG_CONTENT_PATHS_FC:
3802             if (container->default_entry_type == NORMAL_CACHE_ENTRY)
3803                 return container;
3804             break;
3805 
3806         case CACHE_CONFIG_COOKIES_PATHS_FC:
3807             if (container->default_entry_type == COOKIE_CACHE_ENTRY)
3808                 return container;
3809             break;
3810 
3811         case CACHE_CONFIG_HISTORY_PATHS_FC:
3812             if (container->default_entry_type == URLHISTORY_CACHE_ENTRY)
3813                 return container;
3814             break;
3815 
3816         default:
3817             FIXME("flags %08x not handled\n", flags);
3818             break;
3819         }
3820     }
3821 
3822     return NULL;
3823 }
3824 
3825 /***********************************************************************
3826  *           GetUrlCacheConfigInfoW (WININET.@)
3827  */
3828 BOOL WINAPI GetUrlCacheConfigInfoW(LPINTERNET_CACHE_CONFIG_INFOW info, LPDWORD size, DWORD flags)
3829 {
3830     cache_container *container;
3831     DWORD error;
3832 
3833     FIXME("(%p, %p, %x): semi-stub\n", info, size, flags);
3834 
3835     if (!info || !(container = find_container(flags)))
3836     {
3837         INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
3838         return FALSE;
3839     }
3840 
3841     error = cache_container_open_index(container, MIN_BLOCK_NO);
3842     if (error != ERROR_SUCCESS)
3843     {
3844         INTERNET_SetLastError(error);
3845         return FALSE;
3846     }
3847 
3848     info->dwContainer = 0;
3849     info->dwQuota = FILE_SIZE(MAX_BLOCK_NO) / 1024;
3850     info->dwReserved4 = 0;
3851     info->fPerUser = TRUE;
3852     info->dwSyncMode = 0;
3853     info->dwNumCachePaths = 1;
3854     info->dwNormalUsage = 0;
3855     info->dwExemptUsage = 0;
3856     info->u.s.dwCacheSize = container->file_size / 1024;
3857     lstrcpynW(info->u.s.CachePath, container->path, MAX_PATH);
3858 
3859     cache_container_close_index(container);
3860 
3861     TRACE("CachePath %s\n", debugstr_w(info->u.s.CachePath));
3862 
3863     return TRUE;
3864 }
3865 
3866 /***********************************************************************
3867  *           GetUrlCacheConfigInfoA (WININET.@)
3868  */
3869 BOOL WINAPI GetUrlCacheConfigInfoA(LPINTERNET_CACHE_CONFIG_INFOA info, LPDWORD size, DWORD flags)
3870 {
3871     INTERNET_CACHE_CONFIG_INFOW infoW;
3872 
3873     TRACE("(%p, %p, %x)\n", info, size, flags);
3874 
3875     if (!info)
3876     {
3877         INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
3878         return FALSE;
3879     }
3880 
3881     infoW.dwStructSize = sizeof(infoW);
3882     if (!GetUrlCacheConfigInfoW(&infoW, size, flags))
3883         return FALSE;
3884 
3885     info->dwContainer = infoW.dwContainer;
3886     info->dwQuota = infoW.dwQuota;
3887     info->dwReserved4 = infoW.dwReserved4;
3888     info->fPerUser = infoW.fPerUser;
3889     info->dwSyncMode = infoW.dwSyncMode;
3890     info->dwNumCachePaths = infoW.dwNumCachePaths;
3891     info->dwNormalUsage = infoW.dwNormalUsage;
3892     info->dwExemptUsage = infoW.dwExemptUsage;
3893     info->u.s.dwCacheSize = infoW.u.s.dwCacheSize;
3894     WideCharToMultiByte(CP_ACP, 0, infoW.u.s.CachePath, -1, info->u.s.CachePath, MAX_PATH, NULL, NULL);
3895 
3896     return TRUE;
3897 }
3898 
3899 BOOL WINAPI GetUrlCacheGroupAttributeA( GROUPID gid, DWORD dwFlags, DWORD dwAttributes,
3900                                         LPINTERNET_CACHE_GROUP_INFOA lpGroupInfo,
3901                                         LPDWORD lpdwGroupInfo, LPVOID lpReserved )
3902 {
3903     FIXME("(0x%s, 0x%08x, 0x%08x, %p, %p, %p) stub\n",
3904           wine_dbgstr_longlong(gid), dwFlags, dwAttributes, lpGroupInfo,
3905           lpdwGroupInfo, lpReserved);
3906     return FALSE;
3907 }
3908 
3909 BOOL WINAPI GetUrlCacheGroupAttributeW( GROUPID gid, DWORD dwFlags, DWORD dwAttributes,
3910                                         LPINTERNET_CACHE_GROUP_INFOW lpGroupInfo,
3911                                         LPDWORD lpdwGroupInfo, LPVOID lpReserved )
3912 {
3913     FIXME("(0x%s, 0x%08x, 0x%08x, %p, %p, %p) stub\n",
3914           wine_dbgstr_longlong(gid), dwFlags, dwAttributes, lpGroupInfo,
3915           lpdwGroupInfo, lpReserved);
3916     return FALSE;
3917 }
3918 
3919 BOOL WINAPI SetUrlCacheGroupAttributeA( GROUPID gid, DWORD dwFlags, DWORD dwAttributes,
3920                                         LPINTERNET_CACHE_GROUP_INFOA lpGroupInfo, LPVOID lpReserved )
3921 {
3922     FIXME("(0x%s, 0x%08x, 0x%08x, %p, %p) stub\n",
3923           wine_dbgstr_longlong(gid), dwFlags, dwAttributes, lpGroupInfo, lpReserved);
3924     return TRUE;
3925 }
3926 
3927 BOOL WINAPI SetUrlCacheGroupAttributeW( GROUPID gid, DWORD dwFlags, DWORD dwAttributes,
3928                                         LPINTERNET_CACHE_GROUP_INFOW lpGroupInfo, LPVOID lpReserved )
3929 {
3930     FIXME("(0x%s, 0x%08x, 0x%08x, %p, %p) stub\n",
3931           wine_dbgstr_longlong(gid), dwFlags, dwAttributes, lpGroupInfo, lpReserved);
3932     return TRUE;
3933 }
3934 
3935 BOOL WINAPI SetUrlCacheConfigInfoA( LPINTERNET_CACHE_CONFIG_INFOA lpCacheConfigInfo, DWORD dwFieldControl )
3936 {
3937     FIXME("(%p, 0x%08x) stub\n", lpCacheConfigInfo, dwFieldControl);
3938     return TRUE;
3939 }
3940 
3941 BOOL WINAPI SetUrlCacheConfigInfoW( LPINTERNET_CACHE_CONFIG_INFOW lpCacheConfigInfo, DWORD dwFieldControl )
3942 {
3943     FIXME("(%p, 0x%08x) stub\n", lpCacheConfigInfo, dwFieldControl);
3944     return TRUE;
3945 }
3946 
3947 /***********************************************************************
3948  *           DeleteIE3Cache (WININET.@)
3949  *
3950  * Deletes the files used by the IE3 URL caching system.
3951  *
3952  * PARAMS
3953  *   hWnd        [I] A dummy window.
3954  *   hInst       [I] Instance of process calling the function.
3955  *   lpszCmdLine [I] Options used by function.
3956  *   nCmdShow    [I] The nCmdShow value to use when showing windows created, if any.
3957  */
3958 DWORD WINAPI DeleteIE3Cache(HWND hWnd, HINSTANCE hInst, LPSTR lpszCmdLine, int nCmdShow)
3959 {
3960     FIXME("(%p, %p, %s, %d)\n", hWnd, hInst, debugstr_a(lpszCmdLine), nCmdShow);
3961     return 0;
3962 }
3963 
3964 static BOOL urlcache_entry_is_expired(const entry_url *pUrlEntry,
3965         FILETIME *pftLastModified)
3966 {
3967     BOOL ret;
3968     FILETIME now, expired;
3969 
3970     *pftLastModified = pUrlEntry->modification_time;
3971     GetSystemTimeAsFileTime(&now);
3972     dos_date_time_to_file_time(pUrlEntry->expire_date,
3973             pUrlEntry->expire_time, &expired);
3974     /* If the expired time is 0, it's interpreted as not expired */
3975     if (!expired.dwLowDateTime && !expired.dwHighDateTime)
3976         ret = FALSE;
3977     else
3978         ret = CompareFileTime(&expired, &now) < 0;
3979     return ret;
3980 }
3981 
3982 /***********************************************************************
3983  *           IsUrlCacheEntryExpiredA (WININET.@)
3984  *
3985  * PARAMS
3986  *   url             [I] Url
3987  *   dwFlags         [I] Unknown
3988  *   pftLastModified [O] Last modified time
3989  */
3990 BOOL WINAPI IsUrlCacheEntryExpiredA(LPCSTR url, DWORD dwFlags, FILETIME* pftLastModified)
3991 {
3992     urlcache_header *pHeader;
3993     struct hash_entry *pHashEntry;
3994     const entry_header *pEntry;
3995     const entry_url * pUrlEntry;
3996     cache_container *pContainer;
3997     BOOL expired;
3998 
3999     TRACE("(%s, %08x, %p)\n", debugstr_a(url), dwFlags, pftLastModified);
4000 
4001     if (!url || !pftLastModified)
4002         return TRUE;
4003     if (dwFlags)
4004         FIXME("unknown flags 0x%08x\n", dwFlags);
4005 
4006     /* Any error implies that the URL is expired, i.e. not in the cache */
4007     if (cache_containers_find(url, &pContainer))
4008     {
4009         memset(pftLastModified, 0, sizeof(*pftLastModified));
4010         return TRUE;
4011     }
4012 
4013     if (cache_container_open_index(pContainer, MIN_BLOCK_NO))
4014     {
4015         memset(pftLastModified, 0, sizeof(*pftLastModified));
4016         return TRUE;
4017     }
4018 
4019     if (!(pHeader = cache_container_lock_index(pContainer)))
4020     {
4021         memset(pftLastModified, 0, sizeof(*pftLastModified));
4022         return TRUE;
4023     }
4024 
4025     if (!urlcache_find_hash_entry(pHeader, url, &pHashEntry))
4026     {
4027         cache_container_unlock_index(pContainer, pHeader);
4028         memset(pftLastModified, 0, sizeof(*pftLastModified));
4029         TRACE("entry %s not found!\n", url);
4030         return TRUE;
4031     }
4032 
4033     pEntry = (const entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
4034     if (pEntry->signature != URL_SIGNATURE)
4035     {
4036         cache_container_unlock_index(pContainer, pHeader);
4037         memset(pftLastModified, 0, sizeof(*pftLastModified));
4038         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPCSTR)&pEntry->signature, sizeof(DWORD)));
4039         return TRUE;
4040     }
4041 
4042     pUrlEntry = (const entry_url *)pEntry;
4043     expired = urlcache_entry_is_expired(pUrlEntry, pftLastModified);
4044 
4045     cache_container_unlock_index(pContainer, pHeader);
4046 
4047     return expired;
4048 }
4049 
4050 /***********************************************************************
4051  *           IsUrlCacheEntryExpiredW (WININET.@)
4052  *
4053  * PARAMS
4054  *   url             [I] Url
4055  *   dwFlags         [I] Unknown
4056  *   pftLastModified [O] Last modified time
4057  */
4058 BOOL WINAPI IsUrlCacheEntryExpiredW(LPCWSTR url, DWORD dwFlags, FILETIME* pftLastModified)
4059 {
4060     char *encoded_url;
4061     BOOL ret;
4062 
4063     if(!urlcache_encode_url_alloc(url, &encoded_url))
4064         return FALSE;
4065 
4066     ret = IsUrlCacheEntryExpiredA(encoded_url, dwFlags, pftLastModified);
4067     heap_free(encoded_url);
4068     return ret;
4069 }
4070 
4071 /***********************************************************************
4072  *           GetDiskInfoA (WININET.@)
4073  */
4074 BOOL WINAPI GetDiskInfoA(PCSTR path, PDWORD cluster_size, PDWORDLONG free, PDWORDLONG total)
4075 {
4076     BOOL ret;
4077     ULARGE_INTEGER bytes_free, bytes_total;
4078 
4079     TRACE("(%s, %p, %p, %p)\n", debugstr_a(path), cluster_size, free, total);
4080 
4081     if (!path)
4082     {
4083         SetLastError(ERROR_INVALID_PARAMETER);
4084         return FALSE;
4085     }
4086 
4087     if ((ret = GetDiskFreeSpaceExA(path, NULL, &bytes_total, &bytes_free)))
4088     {
4089         if (cluster_size) *cluster_size = 1;
4090         if (free) *free = bytes_free.QuadPart;
4091         if (total) *total = bytes_total.QuadPart;
4092     }
4093     return ret;
4094 }
4095 
4096 /***********************************************************************
4097  *           RegisterUrlCacheNotification (WININET.@)
4098  */
4099 DWORD WINAPI RegisterUrlCacheNotification(LPVOID a, DWORD b, DWORD c, DWORD d, DWORD e, DWORD f)
4100 {
4101     FIXME("(%p %x %x %x %x %x)\n", a, b, c, d, e, f);
4102     return 0;
4103 }
4104 
4105 /***********************************************************************
4106  *           IncrementUrlCacheHeaderData (WININET.@)
4107  */
4108 BOOL WINAPI IncrementUrlCacheHeaderData(DWORD index, LPDWORD data)
4109 {
4110     FIXME("(%u, %p)\n", index, data);
4111     return FALSE;
4112 }
4113 
4114 /***********************************************************************
4115  *           RunOnceUrlCache (WININET.@)
4116  */
4117 
4118 DWORD WINAPI RunOnceUrlCache(HWND hwnd, HINSTANCE hinst, LPSTR cmd, int cmdshow)
4119 {
4120     FIXME("(%p, %p, %s, %d): stub\n", hwnd, hinst, debugstr_a(cmd), cmdshow);
4121     return 0;
4122 }
4123 
4124 BOOL init_urlcache(void)
4125 {
4126     dll_unload_event = CreateEventW(NULL, FALSE, FALSE, NULL);
4127     if(!dll_unload_event)
4128         return FALSE;
4129 
4130     free_cache_running = CreateSemaphoreW(NULL, 1, 1, NULL);
4131     if(!free_cache_running) {
4132         CloseHandle(dll_unload_event);
4133         return FALSE;
4134     }
4135 
4136 #ifndef __REACTOS__
4137     cache_containers_init();
4138 #endif
4139     return TRUE;
4140 }
4141 
4142 void free_urlcache(void)
4143 {
4144     SetEvent(dll_unload_event);
4145     WaitForSingleObject(free_cache_running, INFINITE);
4146     ReleaseSemaphore(free_cache_running, 1, NULL);
4147     CloseHandle(free_cache_running);
4148     CloseHandle(dll_unload_event);
4149 
4150     cache_containers_free();
4151 }
4152 
4153 /***********************************************************************
4154  *           LoadUrlCacheContent (WININET.@)
4155  */
4156 BOOL WINAPI LoadUrlCacheContent(void)
4157 {
4158     FIXME("stub!\n");
4159     return FALSE;
4160 }
4161