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