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