[WININET]
[reactos.git] / reactos / dll / win32 / wininet / urlcache.c
1 /*
2 * Wininet - Url Cache functions
3 *
4 * Copyright 2001,2002 CodeWeavers
5 * Copyright 2003-2008 Robert Shearman
6 *
7 * Eric Kohl
8 * Aric Stewart
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25 #include "internet.h"
26
27 #include <shlobj.h>
28 #include <shellapi.h>
29
30 static const char urlcache_ver_prefix[] = "WINE URLCache Ver ";
31 static const char urlcache_ver[] = "0.2012001";
32
33 #ifndef CHAR_BIT
34 #define CHAR_BIT (8 * sizeof(CHAR))
35 #endif
36
37 #define ENTRY_START_OFFSET 0x4000
38 #define DIR_LENGTH 8
39 #define MAX_DIR_NO 0x20
40 #define BLOCKSIZE 128
41 #define HASHTABLE_SIZE 448
42 #define HASHTABLE_NUM_ENTRIES 64 /* this needs to be power of 2, that divides HASHTABLE_SIZE */
43 #define HASHTABLE_BLOCKSIZE (HASHTABLE_SIZE / HASHTABLE_NUM_ENTRIES)
44 #define ALLOCATION_TABLE_OFFSET 0x250
45 #define ALLOCATION_TABLE_SIZE (ENTRY_START_OFFSET - ALLOCATION_TABLE_OFFSET)
46 #define MIN_BLOCK_NO 0x80
47 #define MAX_BLOCK_NO (ALLOCATION_TABLE_SIZE * CHAR_BIT)
48 #define FILE_SIZE(blocks) ((blocks) * BLOCKSIZE + ENTRY_START_OFFSET)
49
50 #define HASHTABLE_URL 0
51 #define HASHTABLE_DEL 1
52 #define HASHTABLE_LOCK 2
53 #define HASHTABLE_FREE 3
54 #define HASHTABLE_REDR 5
55 #define HASHTABLE_FLAG_BITS 6
56
57 #define PENDING_DELETE_CACHE_ENTRY 0x00400000
58 #define INSTALLED_CACHE_ENTRY 0x10000000
59 #define GET_INSTALLED_ENTRY 0x200
60 #define CACHE_CONTAINER_NO_SUBDIR 0xFE
61
62 #define CACHE_HEADER_DATA_ROOT_LEAK_OFFSET 0x16
63
64 #define FILETIME_SECOND 10000000
65
66 #define DWORD_SIG(a,b,c,d) (a | (b << 8) | (c << 16) | (d << 24))
67 #define URL_SIGNATURE DWORD_SIG('U','R','L',' ')
68 #define REDR_SIGNATURE DWORD_SIG('R','E','D','R')
69 #define LEAK_SIGNATURE DWORD_SIG('L','E','A','K')
70 #define HASH_SIGNATURE DWORD_SIG('H','A','S','H')
71
72 #define DWORD_ALIGN(x) ( (DWORD)(((DWORD)(x)+sizeof(DWORD)-1)/sizeof(DWORD))*sizeof(DWORD) )
73
74 #define URLCACHE_FIND_ENTRY_HANDLE_MAGIC 0xF389ABCD
75
76 typedef struct
77 {
78 DWORD signature;
79 DWORD blocks_used; /* number of 128byte blocks used by this entry */
80 } entry_header;
81
82 typedef struct
83 {
84 entry_header header;
85 FILETIME modification_time;
86 FILETIME access_time;
87 WORD expire_date; /* expire date in dos format */
88 WORD expire_time; /* expire time in dos format */
89 DWORD unk1; /* usually zero */
90 ULARGE_INTEGER size; /* see INTERNET_CACHE_ENTRY_INFO::dwSizeLow/High */
91 DWORD unk2; /* usually zero */
92 DWORD exempt_delta; /* see INTERNET_CACHE_ENTRY_INFO::dwExemptDelta */
93 DWORD unk3; /* usually 0x60 */
94 DWORD url_off; /* offset of start of url from start of entry */
95 BYTE cache_dir; /* index of cache directory this url is stored in */
96 BYTE unk4; /* usually zero */
97 WORD unk5; /* usually 0x1010 */
98 DWORD local_name_off; /* offset of start of local filename from start of entry */
99 DWORD cache_entry_type; /* see INTERNET_CACHE_ENTRY_INFO::CacheEntryType */
100 DWORD header_info_off; /* offset of start of header info from start of entry */
101 DWORD header_info_size;
102 DWORD file_extension_off; /* offset of start of file extension from start of entry */
103 WORD sync_date; /* last sync date in dos format */
104 WORD sync_time; /* last sync time in dos format */
105 DWORD hit_rate; /* see INTERNET_CACHE_ENTRY_INFO::dwHitRate */
106 DWORD use_count; /* see INTERNET_CACHE_ENTRY_INFO::dwUseCount */
107 WORD write_date;
108 WORD write_time;
109 DWORD unk7; /* usually zero */
110 DWORD unk8; /* usually zero */
111 /* packing to dword align start of next field */
112 /* CHAR szSourceUrlName[]; (url) */
113 /* packing to dword align start of next field */
114 /* CHAR szLocalFileName[]; (local file name excluding path) */
115 /* packing to dword align start of next field */
116 /* CHAR szHeaderInfo[]; (header info) */
117 } entry_url;
118
119 struct hash_entry
120 {
121 DWORD key;
122 DWORD offset;
123 };
124
125 typedef struct
126 {
127 entry_header header;
128 DWORD next;
129 DWORD id;
130 struct hash_entry hash_table[HASHTABLE_SIZE];
131 } entry_hash_table;
132
133 typedef struct
134 {
135 char signature[28];
136 DWORD size;
137 DWORD hash_table_off;
138 DWORD capacity_in_blocks;
139 DWORD blocks_in_use;
140 DWORD unk1;
141 ULARGE_INTEGER cache_limit;
142 ULARGE_INTEGER cache_usage;
143 ULARGE_INTEGER exempt_usage;
144 DWORD dirs_no;
145 struct _directory_data
146 {
147 DWORD files_no;
148 char name[DIR_LENGTH];
149 } directory_data[MAX_DIR_NO];
150 DWORD options[0x21];
151 BYTE allocation_table[ALLOCATION_TABLE_SIZE];
152 } urlcache_header;
153
154 typedef struct
155 {
156 HANDLE file;
157 CHAR url[1];
158 } stream_handle;
159
160 typedef struct
161 {
162 struct list entry; /* part of a list */
163 char *cache_prefix; /* string that has to be prefixed for this container to be used */
164 LPWSTR path; /* path to url container directory */
165 HANDLE mapping; /* handle of file mapping */
166 DWORD file_size; /* size of file when mapping was opened */
167 HANDLE mutex; /* handle of mutex */
168 DWORD default_entry_type;
169 } cache_container;
170
171 typedef struct
172 {
173 DWORD magic;
174 char *url_search_pattern;
175 DWORD container_idx;
176 DWORD hash_table_idx;
177 DWORD hash_entry_idx;
178 } find_handle;
179
180 /* List of all containers available */
181 static struct list UrlContainers = LIST_INIT(UrlContainers);
182 /* ReactOS r54992 */
183 BOOL bDefaultContainersAdded = FALSE;
184
185 static inline char *heap_strdupWtoUTF8(LPCWSTR str)
186 {
187 char *ret = NULL;
188
189 if(str) {
190 DWORD size = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
191 ret = heap_alloc(size);
192 if(ret)
193 WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
194 }
195
196 return ret;
197 }
198
199 /***********************************************************************
200 * urlcache_block_is_free (Internal)
201 *
202 * Is the specified block number free?
203 *
204 * RETURNS
205 * zero if free
206 * non-zero otherwise
207 *
208 */
209 static inline BYTE urlcache_block_is_free(BYTE *allocation_table, DWORD block_number)
210 {
211 BYTE mask = 1 << (block_number%CHAR_BIT);
212 return (allocation_table[block_number/CHAR_BIT] & mask) == 0;
213 }
214
215 /***********************************************************************
216 * urlcache_block_free (Internal)
217 *
218 * Marks the specified block as free
219 *
220 * CAUTION
221 * this function is not updating used blocks count
222 *
223 * RETURNS
224 * nothing
225 *
226 */
227 static inline void urlcache_block_free(BYTE *allocation_table, DWORD block_number)
228 {
229 BYTE mask = ~(1 << (block_number%CHAR_BIT));
230 allocation_table[block_number/CHAR_BIT] &= mask;
231 }
232
233 /***********************************************************************
234 * urlcache_block_alloc (Internal)
235 *
236 * Marks the specified block as allocated
237 *
238 * CAUTION
239 * this function is not updating used blocks count
240 *
241 * RETURNS
242 * nothing
243 *
244 */
245 static inline void urlcache_block_alloc(BYTE *allocation_table, DWORD block_number)
246 {
247 BYTE mask = 1 << (block_number%CHAR_BIT);
248 allocation_table[block_number/CHAR_BIT] |= mask;
249 }
250
251 /***********************************************************************
252 * urlcache_entry_alloc (Internal)
253 *
254 * Finds and allocates the first block of free space big enough and
255 * sets entry to point to it.
256 *
257 * RETURNS
258 * ERROR_SUCCESS when free memory block was found
259 * Any other Win32 error code if the entry could not be added
260 *
261 */
262 static DWORD urlcache_entry_alloc(urlcache_header *header, DWORD blocks_needed, entry_header **entry)
263 {
264 DWORD block, block_size;
265
266 for(block=0; block<header->capacity_in_blocks; block+=block_size+1)
267 {
268 block_size = 0;
269 while(block_size<blocks_needed && block_size+block<header->capacity_in_blocks
270 && urlcache_block_is_free(header->allocation_table, block+block_size))
271 block_size++;
272
273 if(block_size == blocks_needed)
274 {
275 DWORD index;
276
277 TRACE("Found free blocks starting at no. %d (0x%x)\n", block, ENTRY_START_OFFSET+block*BLOCKSIZE);
278
279 for(index=0; index<blocks_needed; index++)
280 urlcache_block_alloc(header->allocation_table, block+index);
281
282 *entry = (entry_header*)((BYTE*)header+ENTRY_START_OFFSET+block*BLOCKSIZE);
283 for(index=0; index<blocks_needed*BLOCKSIZE/sizeof(DWORD); index++)
284 ((DWORD*)*entry)[index] = 0xdeadbeef;
285 (*entry)->blocks_used = blocks_needed;
286
287 header->blocks_in_use += blocks_needed;
288 return ERROR_SUCCESS;
289 }
290 }
291
292 return ERROR_HANDLE_DISK_FULL;
293 }
294
295 /***********************************************************************
296 * urlcache_entry_free (Internal)
297 *
298 * Deletes the specified entry and frees the space allocated to it
299 *
300 * RETURNS
301 * TRUE if it succeeded
302 * FALSE if it failed
303 *
304 */
305 static BOOL urlcache_entry_free(urlcache_header *header, entry_header *entry)
306 {
307 DWORD start_block, block;
308
309 /* update allocation table */
310 start_block = ((DWORD)((BYTE*)entry - (BYTE*)header) - ENTRY_START_OFFSET) / BLOCKSIZE;
311 for(block = start_block; block < start_block+entry->blocks_used; block++)
312 urlcache_block_free(header->allocation_table, block);
313
314 header->blocks_in_use -= entry->blocks_used;
315 return TRUE;
316 }
317
318 /***********************************************************************
319 * urlcache_create_hash_table (Internal)
320 *
321 * Creates a new hash table in free space and adds it to the chain of existing
322 * hash tables.
323 *
324 * RETURNS
325 * ERROR_SUCCESS if the hash table was created
326 * ERROR_DISK_FULL if the hash table could not be created
327 *
328 */
329 static DWORD urlcache_create_hash_table(urlcache_header *header, entry_hash_table *hash_table_prev, entry_hash_table **hash_table)
330 {
331 DWORD dwOffset, error;
332 int i;
333
334 if((error = urlcache_entry_alloc(header, 0x20, (entry_header**)hash_table)) != ERROR_SUCCESS)
335 return error;
336
337 dwOffset = (BYTE*)*hash_table-(BYTE*)header;
338
339 if(hash_table_prev)
340 hash_table_prev->next = dwOffset;
341 else
342 header->hash_table_off = dwOffset;
343
344 (*hash_table)->header.signature = HASH_SIGNATURE;
345 (*hash_table)->next = 0;
346 (*hash_table)->id = hash_table_prev ? hash_table_prev->id+1 : 0;
347 for(i = 0; i < HASHTABLE_SIZE; i++) {
348 (*hash_table)->hash_table[i].offset = HASHTABLE_FREE;
349 (*hash_table)->hash_table[i].key = HASHTABLE_FREE;
350 }
351 return ERROR_SUCCESS;
352 }
353
354 /***********************************************************************
355 * cache_container_create_object_name (Internal)
356 *
357 * Converts a path to a name suitable for use as a Win32 object name.
358 * Replaces '\\' characters in-place with the specified character
359 * (usually '_' or '!')
360 *
361 * RETURNS
362 * nothing
363 *
364 */
365 static void cache_container_create_object_name(LPWSTR lpszPath, WCHAR replace)
366 {
367 for (; *lpszPath; lpszPath++)
368 {
369 if (*lpszPath == '\\')
370 *lpszPath = replace;
371 }
372 }
373
374 /* Caller must hold container lock */
375 static HANDLE cache_container_map_index(HANDLE file, const WCHAR *path, DWORD size, BOOL *validate)
376 {
377 static const WCHAR mapping_name_format[]
378 = {'%','s','i','n','d','e','x','.','d','a','t','_','%','l','u',0};
379 WCHAR mapping_name[MAX_PATH];
380 HANDLE mapping;
381
382 wsprintfW(mapping_name, mapping_name_format, path, size);
383 cache_container_create_object_name(mapping_name, '_');
384
385 mapping = OpenFileMappingW(FILE_MAP_WRITE, FALSE, mapping_name);
386 if(mapping) {
387 if(validate) *validate = FALSE;
388 return mapping;
389 }
390
391 if(validate) *validate = TRUE;
392 return CreateFileMappingW(file, NULL, PAGE_READWRITE, 0, 0, mapping_name);
393 }
394
395 /* Caller must hold container lock */
396 static DWORD cache_container_set_size(cache_container *container, HANDLE file, DWORD blocks_no)
397 {
398 static const WCHAR cache_content_key[] = {'S','o','f','t','w','a','r','e','\\',
399 'M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\',
400 'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
401 'I','n','t','e','r','n','e','t',' ','S','e','t','t','i','n','g','s','\\',
402 'C','a','c','h','e','\\','C','o','n','t','e','n','t',0};
403 static const WCHAR cache_limit[] = {'C','a','c','h','e','L','i','m','i','t',0};
404
405 DWORD file_size = FILE_SIZE(blocks_no);
406 WCHAR dir_path[MAX_PATH], *dir_name;
407 entry_hash_table *hashtable_entry;
408 urlcache_header *header;
409 HANDLE mapping;
410 FILETIME ft;
411 HKEY key;
412 int i, j;
413
414 if(SetFilePointer(file, file_size, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
415 return GetLastError();
416
417 if(!SetEndOfFile(file))
418 return GetLastError();
419
420 mapping = cache_container_map_index(file, container->path, file_size, NULL);
421 if(!mapping)
422 return GetLastError();
423
424 header = MapViewOfFile(mapping, FILE_MAP_WRITE, 0, 0, 0);
425 if(!header) {
426 CloseHandle(mapping);
427 return GetLastError();
428 }
429
430 if(blocks_no != MIN_BLOCK_NO) {
431 if(file_size > header->size)
432 memset((char*)header+header->size, 0, file_size-header->size);
433 header->size = file_size;
434 header->capacity_in_blocks = blocks_no;
435
436 UnmapViewOfFile(header);
437 CloseHandle(container->mapping);
438 container->mapping = mapping;
439 container->file_size = file_size;
440 return ERROR_SUCCESS;
441 }
442
443 memset(header, 0, file_size);
444 /* First set some constants and defaults in the header */
445 memcpy(header->signature, urlcache_ver_prefix, sizeof(urlcache_ver_prefix)-1);
446 memcpy(header->signature+sizeof(urlcache_ver_prefix)-1, urlcache_ver, sizeof(urlcache_ver)-1);
447 header->size = file_size;
448 header->capacity_in_blocks = blocks_no;
449 /* 127MB - taken from default for Windows 2000 */
450 header->cache_limit.QuadPart = 0x07ff5400;
451 /* Copied from a Windows 2000 cache index */
452 header->dirs_no = container->default_entry_type==NORMAL_CACHE_ENTRY ? 4 : 0;
453
454 /* If the registry has a cache size set, use the registry value */
455 if(RegOpenKeyW(HKEY_CURRENT_USER, cache_content_key, &key) == ERROR_SUCCESS) {
456 DWORD dw, len = sizeof(dw), keytype;
457
458 if(RegQueryValueExW(key, cache_limit, NULL, &keytype, (BYTE*)&dw, &len) == ERROR_SUCCESS &&
459 keytype == REG_DWORD)
460 header->cache_limit.QuadPart = (ULONGLONG)dw * 1024;
461 RegCloseKey(key);
462 }
463
464 urlcache_create_hash_table(header, NULL, &hashtable_entry);
465
466 /* Last step - create the directories */
467 strcpyW(dir_path, container->path);
468 dir_name = dir_path + strlenW(dir_path);
469 dir_name[8] = 0;
470
471 GetSystemTimeAsFileTime(&ft);
472
473 for(i=0; i<header->dirs_no; ++i) {
474 header->directory_data[i].files_no = 0;
475 for(j=0;; ++j) {
476 ULONGLONG n = ft.dwHighDateTime;
477 int k;
478
479 /* Generate a file name to attempt to create.
480 * This algorithm will create what will appear
481 * to be random and unrelated directory names
482 * of up to 9 characters in length.
483 */
484 n <<= 32;
485 n += ft.dwLowDateTime;
486 n ^= ((ULONGLONG) i << 56) | ((ULONGLONG) j << 48);
487
488 for(k = 0; k < 8; ++k) {
489 int r = (n % 36);
490
491 /* Dividing by a prime greater than 36 helps
492 * with the appearance of randomness
493 */
494 n /= 37;
495
496 if(r < 10)
497 dir_name[k] = '0' + r;
498 else
499 dir_name[k] = 'A' + (r - 10);
500 }
501
502 if(CreateDirectoryW(dir_path, 0)) {
503 /* The following is OK because we generated an
504 * 8 character directory name made from characters
505 * [A-Z0-9], which are equivalent for all code
506 * pages and for UTF-16
507 */
508 for (k = 0; k < 8; ++k)
509 header->directory_data[i].name[k] = dir_name[k];
510 break;
511 }else if(j >= 255) {
512 /* Give up. The most likely cause of this
513 * is a full disk, but whatever the cause
514 * is, it should be more than apparent that
515 * we won't succeed.
516 */
517 UnmapViewOfFile(header);
518 CloseHandle(mapping);
519 return GetLastError();
520 }
521 }
522 }
523
524 UnmapViewOfFile(header);
525 CloseHandle(container->mapping);
526 container->mapping = mapping;
527 container->file_size = file_size;
528 return ERROR_SUCCESS;
529 }
530
531 static BOOL cache_container_is_valid(urlcache_header *header, DWORD file_size)
532 {
533 DWORD allocation_size, count_bits, i;
534
535 if(file_size < FILE_SIZE(MIN_BLOCK_NO))
536 return FALSE;
537
538 if(file_size != header->size)
539 return FALSE;
540
541 if (!memcmp(header->signature, urlcache_ver_prefix, sizeof(urlcache_ver_prefix)-1) &&
542 memcmp(header->signature+sizeof(urlcache_ver_prefix)-1, urlcache_ver, sizeof(urlcache_ver)-1))
543 return FALSE;
544
545 if(FILE_SIZE(header->capacity_in_blocks) != file_size)
546 return FALSE;
547
548 allocation_size = 0;
549 for(i=0; i<header->capacity_in_blocks/8; i++) {
550 for(count_bits = header->allocation_table[i]; count_bits!=0; count_bits>>=1) {
551 if(count_bits & 1)
552 allocation_size++;
553 }
554 }
555 if(allocation_size != header->blocks_in_use)
556 return FALSE;
557
558 for(; i<ALLOCATION_TABLE_SIZE; i++) {
559 if(header->allocation_table[i])
560 return FALSE;
561 }
562
563 return TRUE;
564 }
565
566 /***********************************************************************
567 * cache_container_open_index (Internal)
568 *
569 * Opens the index file and saves mapping handle
570 *
571 * RETURNS
572 * ERROR_SUCCESS if succeeded
573 * Any other Win32 error code if failed
574 *
575 */
576 static DWORD cache_container_open_index(cache_container *container, DWORD blocks_no)
577 {
578 static const WCHAR index_dat[] = {'i','n','d','e','x','.','d','a','t',0};
579
580 HANDLE file;
581 WCHAR index_path[MAX_PATH];
582 DWORD file_size;
583 BOOL validate;
584
585 WaitForSingleObject(container->mutex, INFINITE);
586
587 if(container->mapping) {
588 ReleaseMutex(container->mutex);
589 return ERROR_SUCCESS;
590 }
591
592 strcpyW(index_path, container->path);
593 strcatW(index_path, index_dat);
594
595 file = CreateFileW(index_path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
596 if(file == INVALID_HANDLE_VALUE) {
597 /* Maybe the directory wasn't there? Try to create it */
598 if(CreateDirectoryW(container->path, 0))
599 file = CreateFileW(index_path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
600 }
601 if(file == INVALID_HANDLE_VALUE) {
602 TRACE("Could not open or create cache index file \"%s\"\n", debugstr_w(index_path));
603 ReleaseMutex(container->mutex);
604 return GetLastError();
605 }
606
607 file_size = GetFileSize(file, NULL);
608 if(file_size == INVALID_FILE_SIZE) {
609 CloseHandle(file);
610 ReleaseMutex(container->mutex);
611 return GetLastError();
612 }
613
614 if(blocks_no < MIN_BLOCK_NO)
615 blocks_no = MIN_BLOCK_NO;
616 else if(blocks_no > MAX_BLOCK_NO)
617 blocks_no = MAX_BLOCK_NO;
618
619 if(file_size < FILE_SIZE(blocks_no)) {
620 DWORD ret = cache_container_set_size(container, file, blocks_no);
621 CloseHandle(file);
622 ReleaseMutex(container->mutex);
623 return ret;
624 }
625
626 container->file_size = file_size;
627 container->mapping = cache_container_map_index(file, container->path, file_size, &validate);
628 CloseHandle(file);
629 if(container->mapping && validate) {
630 urlcache_header *header = MapViewOfFile(container->mapping, FILE_MAP_WRITE, 0, 0, 0);
631
632 if(header && !cache_container_is_valid(header, file_size)) {
633 WARN("detected old or broken index.dat file\n");
634 UnmapViewOfFile(header);
635 FreeUrlCacheSpaceW(container->path, 100, 0);
636 }else if(header) {
637 UnmapViewOfFile(header);
638 }else {
639 CloseHandle(container->mapping);
640 container->mapping = NULL;
641 }
642 }
643
644 if(!container->mapping)
645 {
646 ERR("Couldn't create file mapping (error is %d)\n", GetLastError());
647 ReleaseMutex(container->mutex);
648 return GetLastError();
649 }
650
651 ReleaseMutex(container->mutex);
652 return ERROR_SUCCESS;
653 }
654
655 /***********************************************************************
656 * cache_container_close_index (Internal)
657 *
658 * Closes the index
659 *
660 * RETURNS
661 * nothing
662 *
663 */
664 static void cache_container_close_index(cache_container *pContainer)
665 {
666 CloseHandle(pContainer->mapping);
667 pContainer->mapping = NULL;
668 }
669
670 static BOOL cache_containers_add(const char *cache_prefix, LPCWSTR path,
671 DWORD default_entry_type, LPWSTR mutex_name)
672 {
673 cache_container *pContainer = heap_alloc(sizeof(cache_container));
674 int cache_prefix_len = strlen(cache_prefix);
675
676 if (!pContainer)
677 {
678 return FALSE;
679 }
680
681 pContainer->mapping = NULL;
682 pContainer->file_size = 0;
683 pContainer->default_entry_type = default_entry_type;
684
685 pContainer->path = heap_strdupW(path);
686 if (!pContainer->path)
687 {
688 heap_free(pContainer);
689 return FALSE;
690 }
691
692 pContainer->cache_prefix = heap_alloc(cache_prefix_len+1);
693 if (!pContainer->cache_prefix)
694 {
695 heap_free(pContainer->path);
696 heap_free(pContainer);
697 return FALSE;
698 }
699
700 memcpy(pContainer->cache_prefix, cache_prefix, cache_prefix_len+1);
701
702 CharLowerW(mutex_name);
703 cache_container_create_object_name(mutex_name, '!');
704
705 if ((pContainer->mutex = CreateMutexW(NULL, FALSE, mutex_name)) == NULL)
706 {
707 ERR("couldn't create mutex (error is %d)\n", GetLastError());
708 heap_free(pContainer->path);
709 heap_free(pContainer);
710 return FALSE;
711 }
712
713 list_add_head(&UrlContainers, &pContainer->entry);
714
715 return TRUE;
716 }
717
718 static void cache_container_delete_container(cache_container *pContainer)
719 {
720 list_remove(&pContainer->entry);
721
722 cache_container_close_index(pContainer);
723 CloseHandle(pContainer->mutex);
724 heap_free(pContainer->path);
725 heap_free(pContainer->cache_prefix);
726 heap_free(pContainer);
727 }
728
729 static void cache_containers_init(void)
730 {
731 static const WCHAR UrlSuffix[] = {'C','o','n','t','e','n','t','.','I','E','5',0};
732 static const WCHAR HistorySuffix[] = {'H','i','s','t','o','r','y','.','I','E','5',0};
733 static const WCHAR CookieSuffix[] = {0};
734 /* ReactOS r50916 */
735 static const WCHAR UserProfile[] = {'U','S','E','R','P','R','O','F','I','L','E',0};
736 static const struct
737 {
738 int nFolder; /* CSIDL_* constant */
739 const WCHAR *shpath_suffix; /* suffix on path returned by SHGetSpecialFolderPath */
740 const char *cache_prefix; /* prefix used to reference the container */
741 DWORD default_entry_type;
742 } DefaultContainerData[] =
743 {
744 { CSIDL_INTERNET_CACHE, UrlSuffix, "", NORMAL_CACHE_ENTRY },
745 { CSIDL_HISTORY, HistorySuffix, "Visited:", URLHISTORY_CACHE_ENTRY },
746 { CSIDL_COOKIES, CookieSuffix, "Cookie:", COOKIE_CACHE_ENTRY },
747 };
748 DWORD i;
749
750 /* ReactOS r50916 */
751 if (GetEnvironmentVariableW(UserProfile, NULL, 0) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND)
752 {
753 TRACE("Environment variable 'USERPROFILE' does not exist!\n");
754 return;
755 }
756
757 for (i = 0; i < sizeof(DefaultContainerData) / sizeof(DefaultContainerData[0]); i++)
758 {
759 WCHAR wszCachePath[MAX_PATH];
760 WCHAR wszMutexName[MAX_PATH];
761 int path_len, suffix_len;
762 BOOL def_char;
763
764 if (!SHGetSpecialFolderPathW(NULL, wszCachePath, DefaultContainerData[i].nFolder, TRUE))
765 {
766 ERR("Couldn't get path for default container %u\n", i);
767 continue;
768 }
769 path_len = strlenW(wszCachePath);
770 suffix_len = strlenW(DefaultContainerData[i].shpath_suffix);
771
772 if (path_len + suffix_len + 2 > MAX_PATH)
773 {
774 ERR("Path too long\n");
775 continue;
776 }
777
778 wszCachePath[path_len] = '\\';
779 wszCachePath[path_len+1] = 0;
780
781 strcpyW(wszMutexName, wszCachePath);
782
783 if (suffix_len)
784 {
785 memcpy(wszCachePath + path_len + 1, DefaultContainerData[i].shpath_suffix, (suffix_len + 1) * sizeof(WCHAR));
786 wszCachePath[path_len + suffix_len + 1] = '\\';
787 wszCachePath[path_len + suffix_len + 2] = '\0';
788 }
789
790 if (!WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wszCachePath, path_len,
791 NULL, 0, NULL, &def_char) || def_char)
792 {
793 WCHAR tmp[MAX_PATH];
794
795 /* cannot convert path to ANSI code page */
796 if (!(path_len = GetShortPathNameW(wszCachePath, tmp, MAX_PATH)) ||
797 !WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, tmp, path_len,
798 NULL, 0, NULL, &def_char) || def_char)
799 ERR("Can't create container path accessible by ANSI functions\n");
800 else
801 memcpy(wszCachePath, tmp, (path_len+1)*sizeof(WCHAR));
802 }
803
804 cache_containers_add(DefaultContainerData[i].cache_prefix, wszCachePath,
805 DefaultContainerData[i].default_entry_type, wszMutexName);
806 }
807
808 #ifdef __REACTOS__
809 bDefaultContainersAdded = TRUE;
810 #endif
811 }
812
813 static void cache_containers_free(void)
814 {
815 while(!list_empty(&UrlContainers))
816 cache_container_delete_container(
817 LIST_ENTRY(list_head(&UrlContainers), cache_container, entry)
818 );
819 }
820
821 static DWORD cache_containers_find(const char *url, cache_container **ret)
822 {
823 cache_container *container;
824
825 TRACE("searching for prefix for URL: %s\n", debugstr_a(url));
826
827 if(!url)
828 return ERROR_INVALID_PARAMETER;
829
830 #ifdef __REACTOS__
831 /* ReactOS r54992 */
832 if (!bDefaultContainersAdded)
833 cache_containers_init();
834 #endif
835
836 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
837 {
838 int prefix_len = strlen(container->cache_prefix);
839
840 if(!strncmp(container->cache_prefix, url, prefix_len)) {
841 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
842 *ret = container;
843 return ERROR_SUCCESS;
844 }
845 }
846
847 ERR("no container found\n");
848 return ERROR_FILE_NOT_FOUND;
849 }
850
851 static BOOL cache_containers_enum(char *search_pattern, DWORD index, cache_container **ret)
852 {
853 DWORD i = 0;
854 cache_container *container;
855
856 TRACE("searching for prefix: %s\n", debugstr_a(search_pattern));
857
858 /* non-NULL search pattern only returns one container ever */
859 if (search_pattern && index > 0)
860 return FALSE;
861
862 #ifdef __REACTOS__
863 /* ReactOS r54992 */
864 if (!bDefaultContainersAdded)
865 cache_containers_init();
866 #endif
867
868 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
869 {
870 if (search_pattern)
871 {
872 if (!strcmp(container->cache_prefix, search_pattern))
873 {
874 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
875 *ret = container;
876 return TRUE;
877 }
878 }
879 else
880 {
881 if (i == index)
882 {
883 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
884 *ret = container;
885 return TRUE;
886 }
887 }
888 i++;
889 }
890 return FALSE;
891 }
892
893 /***********************************************************************
894 * cache_container_lock_index (Internal)
895 *
896 * Locks the index for system-wide exclusive access.
897 *
898 * RETURNS
899 * Cache file header if successful
900 * NULL if failed and calls SetLastError.
901 */
902 static urlcache_header* cache_container_lock_index(cache_container *pContainer)
903 {
904 BYTE index;
905 LPVOID pIndexData;
906 urlcache_header* pHeader;
907 DWORD error;
908
909 /* acquire mutex */
910 WaitForSingleObject(pContainer->mutex, INFINITE);
911
912 pIndexData = MapViewOfFile(pContainer->mapping, FILE_MAP_WRITE, 0, 0, 0);
913
914 if (!pIndexData)
915 {
916 ReleaseMutex(pContainer->mutex);
917 ERR("Couldn't MapViewOfFile. Error: %d\n", GetLastError());
918 return NULL;
919 }
920 pHeader = (urlcache_header*)pIndexData;
921
922 /* file has grown - we need to remap to prevent us getting
923 * access violations when we try and access beyond the end
924 * of the memory mapped file */
925 if (pHeader->size != pContainer->file_size)
926 {
927 UnmapViewOfFile( pHeader );
928 cache_container_close_index(pContainer);
929 error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
930 if (error != ERROR_SUCCESS)
931 {
932 ReleaseMutex(pContainer->mutex);
933 SetLastError(error);
934 return NULL;
935 }
936 pIndexData = MapViewOfFile(pContainer->mapping, FILE_MAP_WRITE, 0, 0, 0);
937
938 if (!pIndexData)
939 {
940 ReleaseMutex(pContainer->mutex);
941 ERR("Couldn't MapViewOfFile. Error: %d\n", GetLastError());
942 return NULL;
943 }
944 pHeader = (urlcache_header*)pIndexData;
945 }
946
947 TRACE("Signature: %s, file size: %d bytes\n", pHeader->signature, pHeader->size);
948
949 for (index = 0; index < pHeader->dirs_no; index++)
950 {
951 TRACE("Directory[%d] = \"%.8s\"\n", index, pHeader->directory_data[index].name);
952 }
953
954 return pHeader;
955 }
956
957 /***********************************************************************
958 * cache_container_unlock_index (Internal)
959 *
960 */
961 static BOOL cache_container_unlock_index(cache_container *pContainer, urlcache_header *pHeader)
962 {
963 /* release mutex */
964 ReleaseMutex(pContainer->mutex);
965 return UnmapViewOfFile(pHeader);
966 }
967
968 /***********************************************************************
969 * urlcache_create_file_pathW (Internal)
970 *
971 * Copies the full path to the specified buffer given the local file
972 * name and the index of the directory it is in. Always sets value in
973 * lpBufferSize to the required buffer size (in bytes).
974 *
975 * RETURNS
976 * TRUE if the buffer was big enough
977 * FALSE if the buffer was too small
978 *
979 */
980 static BOOL urlcache_create_file_pathW(
981 const cache_container *pContainer,
982 const urlcache_header *pHeader,
983 LPCSTR szLocalFileName,
984 BYTE Directory,
985 LPWSTR wszPath,
986 LPLONG lpBufferSize,
987 BOOL trunc_name)
988 {
989 LONG nRequired;
990 int path_len = strlenW(pContainer->path);
991 int file_name_len = MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, NULL, 0);
992 if (Directory!=CACHE_CONTAINER_NO_SUBDIR && Directory>=pHeader->dirs_no)
993 {
994 *lpBufferSize = 0;
995 return FALSE;
996 }
997
998 nRequired = (path_len + file_name_len) * sizeof(WCHAR);
999 if(Directory != CACHE_CONTAINER_NO_SUBDIR)
1000 nRequired += (DIR_LENGTH + 1) * sizeof(WCHAR);
1001 if (trunc_name && nRequired >= *lpBufferSize)
1002 nRequired = *lpBufferSize;
1003 if (nRequired <= *lpBufferSize)
1004 {
1005 int dir_len;
1006
1007 memcpy(wszPath, pContainer->path, path_len * sizeof(WCHAR));
1008 if (Directory != CACHE_CONTAINER_NO_SUBDIR)
1009 {
1010 dir_len = MultiByteToWideChar(CP_ACP, 0, pHeader->directory_data[Directory].name, DIR_LENGTH, wszPath + path_len, DIR_LENGTH);
1011 wszPath[dir_len + path_len] = '\\';
1012 dir_len++;
1013 }
1014 else
1015 {
1016 dir_len = 0;
1017 }
1018 MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, wszPath + dir_len + path_len,
1019 *lpBufferSize/sizeof(WCHAR)-dir_len-path_len);
1020 wszPath[*lpBufferSize/sizeof(WCHAR)-1] = 0;
1021 *lpBufferSize = nRequired;
1022 return TRUE;
1023 }
1024 *lpBufferSize = nRequired;
1025 return FALSE;
1026 }
1027
1028 /***********************************************************************
1029 * urlcache_create_file_pathA (Internal)
1030 *
1031 * Copies the full path to the specified buffer given the local file
1032 * name and the index of the directory it is in. Always sets value in
1033 * lpBufferSize to the required buffer size.
1034 *
1035 * RETURNS
1036 * TRUE if the buffer was big enough
1037 * FALSE if the buffer was too small
1038 *
1039 */
1040 static BOOL urlcache_create_file_pathA(
1041 const cache_container *pContainer,
1042 const urlcache_header *pHeader,
1043 LPCSTR szLocalFileName,
1044 BYTE Directory,
1045 LPSTR szPath,
1046 LPLONG lpBufferSize)
1047 {
1048 LONG nRequired;
1049 int path_len, file_name_len, dir_len;
1050
1051 if (Directory!=CACHE_CONTAINER_NO_SUBDIR && Directory>=pHeader->dirs_no)
1052 {
1053 *lpBufferSize = 0;
1054 return FALSE;
1055 }
1056
1057 path_len = WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, NULL, 0, NULL, NULL) - 1;
1058 file_name_len = strlen(szLocalFileName) + 1 /* for nul-terminator */;
1059 if (Directory!=CACHE_CONTAINER_NO_SUBDIR)
1060 dir_len = DIR_LENGTH+1;
1061 else
1062 dir_len = 0;
1063
1064 nRequired = (path_len + dir_len + file_name_len) * sizeof(char);
1065 if (nRequired <= *lpBufferSize)
1066 {
1067 WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, szPath, path_len, NULL, NULL);
1068 if(dir_len) {
1069 memcpy(szPath+path_len, pHeader->directory_data[Directory].name, dir_len-1);
1070 szPath[path_len + dir_len-1] = '\\';
1071 }
1072 memcpy(szPath + path_len + dir_len, szLocalFileName, file_name_len);
1073 *lpBufferSize = nRequired;
1074 return TRUE;
1075 }
1076 *lpBufferSize = nRequired;
1077 return FALSE;
1078 }
1079
1080 /* Just like FileTimeToDosDateTime, except that it also maps the special
1081 * case of a filetime of (0,0) to a DOS date/time of (0,0).
1082 */
1083 static void file_time_to_dos_date_time(const FILETIME *ft, WORD *fatdate,
1084 WORD *fattime)
1085 {
1086 if (!ft->dwLowDateTime && !ft->dwHighDateTime)
1087 *fatdate = *fattime = 0;
1088 else
1089 FileTimeToDosDateTime(ft, fatdate, fattime);
1090 }
1091
1092 /***********************************************************************
1093 * urlcache_delete_file (Internal)
1094 */
1095 static DWORD urlcache_delete_file(const cache_container *container,
1096 urlcache_header *header, entry_url *url_entry)
1097 {
1098 WIN32_FILE_ATTRIBUTE_DATA attr;
1099 WCHAR path[MAX_PATH];
1100 LONG path_size = sizeof(path);
1101 DWORD err;
1102 WORD date, time;
1103
1104 if(!url_entry->local_name_off)
1105 goto succ;
1106
1107 if(!urlcache_create_file_pathW(container, header,
1108 (LPCSTR)url_entry+url_entry->local_name_off,
1109 url_entry->cache_dir, path, &path_size, FALSE))
1110 goto succ;
1111
1112 if(!GetFileAttributesExW(path, GetFileExInfoStandard, &attr))
1113 goto succ;
1114 file_time_to_dos_date_time(&attr.ftLastWriteTime, &date, &time);
1115 if(date != url_entry->write_date || time != url_entry->write_time)
1116 goto succ;
1117
1118 err = (DeleteFileW(path) ? ERROR_SUCCESS : GetLastError());
1119 if(err == ERROR_ACCESS_DENIED || err == ERROR_SHARING_VIOLATION)
1120 return err;
1121
1122 succ:
1123 if (url_entry->cache_dir < header->dirs_no)
1124 {
1125 if (header->directory_data[url_entry->cache_dir].files_no)
1126 header->directory_data[url_entry->cache_dir].files_no--;
1127 }
1128 if (url_entry->cache_entry_type & STICKY_CACHE_ENTRY)
1129 {
1130 if (url_entry->size.QuadPart < header->exempt_usage.QuadPart)
1131 header->exempt_usage.QuadPart -= url_entry->size.QuadPart;
1132 else
1133 header->exempt_usage.QuadPart = 0;
1134 }
1135 else
1136 {
1137 if (url_entry->size.QuadPart < header->cache_usage.QuadPart)
1138 header->cache_usage.QuadPart -= url_entry->size.QuadPart;
1139 else
1140 header->cache_usage.QuadPart = 0;
1141 }
1142
1143 return ERROR_SUCCESS;
1144 }
1145
1146 static BOOL urlcache_clean_leaked_entries(cache_container *container, urlcache_header *header)
1147 {
1148 DWORD *leak_off;
1149 BOOL freed = FALSE;
1150
1151 leak_off = &header->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET];
1152 while(*leak_off) {
1153 entry_url *url_entry = (entry_url*)((LPBYTE)header + *leak_off);
1154
1155 if(SUCCEEDED(urlcache_delete_file(container, header, url_entry))) {
1156 *leak_off = url_entry->exempt_delta;
1157 urlcache_entry_free(header, &url_entry->header);
1158 freed = TRUE;
1159 }else {
1160 leak_off = &url_entry->exempt_delta;
1161 }
1162 }
1163
1164 return freed;
1165 }
1166
1167 /***********************************************************************
1168 * cache_container_clean_index (Internal)
1169 *
1170 * This function is meant to make place in index file by removing leaked
1171 * files entries and resizing the file.
1172 *
1173 * CAUTION: file view may get mapped to new memory
1174 *
1175 * RETURNS
1176 * ERROR_SUCCESS when new memory is available
1177 * error code otherwise
1178 */
1179 static DWORD cache_container_clean_index(cache_container *container, urlcache_header **file_view)
1180 {
1181 urlcache_header *header = *file_view;
1182 DWORD ret;
1183
1184 TRACE("(%s %s)\n", debugstr_a(container->cache_prefix), debugstr_w(container->path));
1185
1186 if(urlcache_clean_leaked_entries(container, header))
1187 return ERROR_SUCCESS;
1188
1189 if(header->size >= ALLOCATION_TABLE_SIZE*8*BLOCKSIZE + ENTRY_START_OFFSET) {
1190 WARN("index file has maximal size\n");
1191 return ERROR_NOT_ENOUGH_MEMORY;
1192 }
1193
1194 cache_container_close_index(container);
1195 ret = cache_container_open_index(container, header->capacity_in_blocks*2);
1196 if(ret != ERROR_SUCCESS)
1197 return ret;
1198 header = MapViewOfFile(container->mapping, FILE_MAP_WRITE, 0, 0, 0);
1199 if(!header)
1200 return GetLastError();
1201
1202 UnmapViewOfFile(*file_view);
1203 *file_view = header;
1204 return ERROR_SUCCESS;
1205 }
1206
1207 /* Just like DosDateTimeToFileTime, except that it also maps the special
1208 * case of a DOS date/time of (0,0) to a filetime of (0,0).
1209 */
1210 static void dos_date_time_to_file_time(WORD fatdate, WORD fattime,
1211 FILETIME *ft)
1212 {
1213 if (!fatdate && !fattime)
1214 ft->dwLowDateTime = ft->dwHighDateTime = 0;
1215 else
1216 DosDateTimeToFileTime(fatdate, fattime, ft);
1217 }
1218
1219 static int urlcache_decode_url(const char *url, WCHAR *decoded_url, int decoded_len)
1220 {
1221 URL_COMPONENTSA uc;
1222 DWORD len, part_len;
1223 WCHAR *host_name;
1224
1225 memset(&uc, 0, sizeof(uc));
1226 uc.dwStructSize = sizeof(uc);
1227 uc.dwHostNameLength = 1;
1228 if(!InternetCrackUrlA(url, 0, 0, &uc))
1229 uc.nScheme = INTERNET_SCHEME_UNKNOWN;
1230
1231 if(uc.nScheme!=INTERNET_SCHEME_HTTP && uc.nScheme!=INTERNET_SCHEME_HTTPS)
1232 return MultiByteToWideChar(CP_UTF8, 0, url, -1, decoded_url, decoded_len);
1233
1234 if(!decoded_url)
1235 decoded_len = 0;
1236
1237 len = MultiByteToWideChar(CP_UTF8, 0, url, uc.lpszHostName-url, decoded_url, decoded_len);
1238 if(!len)
1239 return 0;
1240 if(decoded_url)
1241 decoded_len -= len;
1242
1243 host_name = heap_alloc(uc.dwHostNameLength*sizeof(WCHAR));
1244 if(!host_name)
1245 return 0;
1246 if(!MultiByteToWideChar(CP_UTF8, 0, uc.lpszHostName, uc.dwHostNameLength,
1247 host_name, uc.dwHostNameLength)) {
1248 heap_free(host_name);
1249 return 0;
1250 }
1251 part_len = IdnToUnicode(0, host_name, uc.dwHostNameLength,
1252 decoded_url ? decoded_url+len : NULL, decoded_len);
1253 heap_free(host_name);
1254 if(!part_len) {
1255 SetLastError(ERROR_INTERNET_INVALID_URL);
1256 return 0;
1257 }
1258 len += part_len;
1259 if(decoded_url)
1260 decoded_len -= part_len;
1261
1262 part_len = MultiByteToWideChar(CP_UTF8, 0,
1263 uc.lpszHostName+uc.dwHostNameLength,
1264 -1, decoded_url ? decoded_url+len : NULL, decoded_len);
1265 if(!part_len)
1266 return 0;
1267 len += part_len;
1268
1269 return len;
1270 }
1271
1272 /***********************************************************************
1273 * urlcache_copy_entry (Internal)
1274 *
1275 * Copies an entry from the cache index file to the Win32 structure
1276 *
1277 * RETURNS
1278 * ERROR_SUCCESS if the buffer was big enough
1279 * ERROR_INSUFFICIENT_BUFFER if the buffer was too small
1280 *
1281 */
1282 static DWORD urlcache_copy_entry(cache_container *container, const urlcache_header *header,
1283 INTERNET_CACHE_ENTRY_INFOA *entry_info, DWORD *info_size, const entry_url *url_entry, BOOL unicode)
1284 {
1285 int url_len;
1286 DWORD size = sizeof(*entry_info);
1287
1288 if(*info_size >= size) {
1289 entry_info->lpHeaderInfo = NULL;
1290 entry_info->lpszFileExtension = NULL;
1291 entry_info->lpszLocalFileName = NULL;
1292 entry_info->lpszSourceUrlName = NULL;
1293 entry_info->CacheEntryType = url_entry->cache_entry_type;
1294 entry_info->u.dwExemptDelta = url_entry->exempt_delta;
1295 entry_info->dwHeaderInfoSize = url_entry->header_info_size;
1296 entry_info->dwHitRate = url_entry->hit_rate;
1297 entry_info->dwSizeHigh = url_entry->size.u.HighPart;
1298 entry_info->dwSizeLow = url_entry->size.u.LowPart;
1299 entry_info->dwStructSize = sizeof(*entry_info);
1300 entry_info->dwUseCount = url_entry->use_count;
1301 dos_date_time_to_file_time(url_entry->expire_date, url_entry->expire_time, &entry_info->ExpireTime);
1302 entry_info->LastAccessTime = url_entry->access_time;
1303 entry_info->LastModifiedTime = url_entry->modification_time;
1304 dos_date_time_to_file_time(url_entry->sync_date, url_entry->sync_time, &entry_info->LastSyncTime);
1305 }
1306
1307 if(unicode)
1308 url_len = urlcache_decode_url((const char*)url_entry+url_entry->url_off, NULL, 0);
1309 else
1310 url_len = strlen((LPCSTR)url_entry+url_entry->url_off) + 1;
1311 size += url_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1312
1313 if(*info_size >= size) {
1314 DWORD url_size = url_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1315
1316 entry_info->lpszSourceUrlName = (LPSTR)entry_info+size-url_size;
1317 if(unicode)
1318 urlcache_decode_url((const char*)url_entry+url_entry->url_off, (WCHAR*)entry_info->lpszSourceUrlName, url_len);
1319 else
1320 memcpy(entry_info->lpszSourceUrlName, (LPCSTR)url_entry+url_entry->url_off, url_size);
1321 }
1322
1323 if(size%4 && size<*info_size)
1324 ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1325 size = DWORD_ALIGN(size);
1326
1327 if(url_entry->local_name_off) {
1328 LONG file_name_size;
1329 LPSTR file_name;
1330 file_name = (LPSTR)entry_info+size;
1331 file_name_size = *info_size-size;
1332 if((unicode && urlcache_create_file_pathW(container, header, (LPCSTR)url_entry+url_entry->local_name_off,
1333 url_entry->cache_dir, (LPWSTR)file_name, &file_name_size, FALSE)) ||
1334 (!unicode && urlcache_create_file_pathA(container, header, (LPCSTR)url_entry+url_entry->local_name_off,
1335 url_entry->cache_dir, file_name, &file_name_size))) {
1336 entry_info->lpszLocalFileName = file_name;
1337 }
1338 size += file_name_size;
1339
1340 if(size%4 && size<*info_size)
1341 ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1342 size = DWORD_ALIGN(size);
1343 }
1344
1345 if(url_entry->header_info_off) {
1346 DWORD header_len;
1347
1348 if(unicode)
1349 header_len = MultiByteToWideChar(CP_UTF8, 0, (const char*)url_entry+url_entry->header_info_off,
1350 url_entry->header_info_size, NULL, 0);
1351 else
1352 header_len = url_entry->header_info_size;
1353 size += header_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1354
1355 if(*info_size >= size) {
1356 DWORD header_size = header_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1357 entry_info->lpHeaderInfo = (LPBYTE)entry_info+size-header_size;
1358 if(unicode)
1359 MultiByteToWideChar(CP_UTF8, 0, (const char*)url_entry+url_entry->header_info_off,
1360 url_entry->header_info_size, (LPWSTR)entry_info->lpHeaderInfo, header_len);
1361 else
1362 memcpy(entry_info->lpHeaderInfo, (LPCSTR)url_entry+url_entry->header_info_off, header_len);
1363 }
1364 if(size%4 && size<*info_size)
1365 ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1366 size = DWORD_ALIGN(size);
1367 }
1368
1369 if(url_entry->file_extension_off) {
1370 int ext_len;
1371
1372 if(unicode)
1373 ext_len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)url_entry+url_entry->file_extension_off, -1, NULL, 0);
1374 else
1375 ext_len = strlen((LPCSTR)url_entry+url_entry->file_extension_off) + 1;
1376 size += ext_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1377
1378 if(*info_size >= size) {
1379 DWORD ext_size = ext_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1380 entry_info->lpszFileExtension = (LPSTR)entry_info+size-ext_size;
1381 if(unicode)
1382 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)url_entry+url_entry->file_extension_off, -1, (LPWSTR)entry_info->lpszFileExtension, ext_len);
1383 else
1384 memcpy(entry_info->lpszFileExtension, (LPCSTR)url_entry+url_entry->file_extension_off, ext_len*sizeof(CHAR));
1385 }
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(size > *info_size) {
1393 *info_size = size;
1394 return ERROR_INSUFFICIENT_BUFFER;
1395 }
1396 *info_size = size;
1397 return ERROR_SUCCESS;
1398 }
1399
1400 /***********************************************************************
1401 * urlcache_set_entry_info (Internal)
1402 *
1403 * Helper for SetUrlCacheEntryInfo{A,W}. Sets fields in URL entry
1404 * according to the flags set by field_control.
1405 *
1406 * RETURNS
1407 * ERROR_SUCCESS if the buffer was big enough
1408 * ERROR_INSUFFICIENT_BUFFER if the buffer was too small
1409 *
1410 */
1411 static DWORD urlcache_set_entry_info(entry_url *url_entry, const INTERNET_CACHE_ENTRY_INFOA *entry_info, DWORD field_control)
1412 {
1413 if (field_control & CACHE_ENTRY_ACCTIME_FC)
1414 url_entry->access_time = entry_info->LastAccessTime;
1415 if (field_control & CACHE_ENTRY_ATTRIBUTE_FC)
1416 url_entry->cache_entry_type = entry_info->CacheEntryType;
1417 if (field_control & CACHE_ENTRY_EXEMPT_DELTA_FC)
1418 url_entry->exempt_delta = entry_info->u.dwExemptDelta;
1419 if (field_control & CACHE_ENTRY_EXPTIME_FC)
1420 file_time_to_dos_date_time(&entry_info->ExpireTime, &url_entry->expire_date, &url_entry->expire_time);
1421 if (field_control & CACHE_ENTRY_HEADERINFO_FC)
1422 FIXME("CACHE_ENTRY_HEADERINFO_FC unimplemented\n");
1423 if (field_control & CACHE_ENTRY_HITRATE_FC)
1424 url_entry->hit_rate = entry_info->dwHitRate;
1425 if (field_control & CACHE_ENTRY_MODTIME_FC)
1426 url_entry->modification_time = entry_info->LastModifiedTime;
1427 if (field_control & CACHE_ENTRY_SYNCTIME_FC)
1428 file_time_to_dos_date_time(&entry_info->LastAccessTime, &url_entry->sync_date, &url_entry->sync_time);
1429
1430 return ERROR_SUCCESS;
1431 }
1432
1433 /***********************************************************************
1434 * urlcache_hash_key (Internal)
1435 *
1436 * Returns the hash key for a given string
1437 *
1438 * RETURNS
1439 * hash key for the string
1440 *
1441 */
1442 static DWORD urlcache_hash_key(LPCSTR lpszKey)
1443 {
1444 /* NOTE: this uses the same lookup table as SHLWAPI.UrlHash{A,W}
1445 * but the algorithm and result are not the same!
1446 */
1447 static const unsigned char lookupTable[256] =
1448 {
1449 0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77,
1450 0x8A, 0xAA, 0x7D, 0x76, 0x1B, 0xE9, 0x8C, 0x33,
1451 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44,
1452 0x1E, 0x07, 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41,
1453 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94, 0xDF,
1454 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C,
1455 0x0C, 0xB5, 0x67, 0x46, 0x16, 0x3A, 0x4B, 0x4E,
1456 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90,
1457 0xB0, 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53,
1458 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6, 0x29, 0xFE,
1459 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58,
1460 0x23, 0xCE, 0x5F, 0x74, 0xFC, 0xC0, 0x36, 0xDD,
1461 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
1462 0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D,
1463 0xA6, 0x50, 0x32, 0x22, 0xAF, 0xC3, 0x64, 0x63,
1464 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD,
1465 0x79, 0x40, 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A,
1466 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9, 0xC2,
1467 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B,
1468 0x4A, 0x3B, 0x89, 0xE4, 0x6C, 0xBF, 0xE8, 0x8B,
1469 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C,
1470 0xFB, 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70,
1471 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB, 0x0D, 0x20,
1472 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B,
1473 0xF9, 0xEC, 0x2D, 0xF4, 0x6F, 0xB6, 0x99, 0x88,
1474 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
1475 0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72,
1476 0xA2, 0x35, 0xA0, 0xD7, 0xCD, 0xB4, 0x2F, 0x6D,
1477 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34,
1478 0x3F, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8,
1479 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB, 0x0A,
1480 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1
1481 };
1482 BYTE key[4];
1483 DWORD i;
1484
1485 for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
1486 key[i] = lookupTable[(*lpszKey + i) & 0xFF];
1487
1488 for (lpszKey++; *lpszKey; lpszKey++)
1489 {
1490 for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
1491 key[i] = lookupTable[*lpszKey ^ key[i]];
1492 }
1493
1494 return *(DWORD *)key;
1495 }
1496
1497 static inline entry_hash_table* urlcache_get_hash_table(const urlcache_header *pHeader, DWORD dwOffset)
1498 {
1499 if(!dwOffset)
1500 return NULL;
1501 return (entry_hash_table*)((LPBYTE)pHeader + dwOffset);
1502 }
1503
1504 static BOOL urlcache_find_hash_entry(const urlcache_header *pHeader, LPCSTR lpszUrl, struct hash_entry **ppHashEntry)
1505 {
1506 /* structure of hash table:
1507 * 448 entries divided into 64 blocks
1508 * each block therefore contains a chain of 7 key/offset pairs
1509 * how position in table is calculated:
1510 * 1. the url is hashed in helper function
1511 * 2. the key % HASHTABLE_NUM_ENTRIES is the bucket number
1512 * 3. bucket number * HASHTABLE_BLOCKSIZE is offset of the bucket
1513 *
1514 * note:
1515 * there can be multiple hash tables in the file and the offset to
1516 * the next one is stored in the header of the hash table
1517 */
1518 DWORD key = urlcache_hash_key(lpszUrl);
1519 DWORD offset = (key & (HASHTABLE_NUM_ENTRIES-1)) * HASHTABLE_BLOCKSIZE;
1520 entry_hash_table* pHashEntry;
1521 DWORD id = 0;
1522
1523 key >>= HASHTABLE_FLAG_BITS;
1524
1525 for (pHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1526 pHashEntry; pHashEntry = urlcache_get_hash_table(pHeader, pHashEntry->next))
1527 {
1528 int i;
1529 if (pHashEntry->id != id++)
1530 {
1531 ERR("Error: not right hash table number (%d) expected %d\n", pHashEntry->id, id);
1532 continue;
1533 }
1534 /* make sure that it is in fact a hash entry */
1535 if (pHashEntry->header.signature != HASH_SIGNATURE)
1536 {
1537 ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->header.signature);
1538 continue;
1539 }
1540
1541 for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
1542 {
1543 struct hash_entry *pHashElement = &pHashEntry->hash_table[offset + i];
1544 if (key == pHashElement->key>>HASHTABLE_FLAG_BITS)
1545 {
1546 /* FIXME: we should make sure that this is the right element
1547 * before returning and claiming that it is. We can do this
1548 * by doing a simple compare between the URL we were given
1549 * and the URL stored in the entry. However, this assumes
1550 * we know the format of all the entries stored in the
1551 * hash table */
1552 *ppHashEntry = pHashElement;
1553 return TRUE;
1554 }
1555 }
1556 }
1557 return FALSE;
1558 }
1559
1560 /***********************************************************************
1561 * urlcache_hash_entry_set_flags (Internal)
1562 *
1563 * Sets special bits in hash key
1564 *
1565 * RETURNS
1566 * nothing
1567 *
1568 */
1569 static void urlcache_hash_entry_set_flags(struct hash_entry *pHashEntry, DWORD dwFlag)
1570 {
1571 pHashEntry->key = (pHashEntry->key >> HASHTABLE_FLAG_BITS << HASHTABLE_FLAG_BITS) | dwFlag;
1572 }
1573
1574 /***********************************************************************
1575 * urlcache_hash_entry_delete (Internal)
1576 *
1577 * Searches all the hash tables in the index for the given URL and
1578 * then if found deletes the entry.
1579 *
1580 * RETURNS
1581 * TRUE if the entry was found
1582 * FALSE if the entry could not be found
1583 *
1584 */
1585 static BOOL urlcache_hash_entry_delete(struct hash_entry *pHashEntry)
1586 {
1587 pHashEntry->key = HASHTABLE_DEL;
1588 return TRUE;
1589 }
1590
1591 /***********************************************************************
1592 * urlcache_hash_entry_create (Internal)
1593 *
1594 * Searches all the hash tables for a free slot based on the offset
1595 * generated from the hash key. If a free slot is found, the offset and
1596 * key are entered into the hash table.
1597 *
1598 * RETURNS
1599 * ERROR_SUCCESS if the entry was added
1600 * Any other Win32 error code if the entry could not be added
1601 *
1602 */
1603 static DWORD urlcache_hash_entry_create(urlcache_header *pHeader, LPCSTR lpszUrl, DWORD dwOffsetEntry, DWORD dwFieldType)
1604 {
1605 /* see urlcache_find_hash_entry for structure of hash tables */
1606
1607 DWORD key = urlcache_hash_key(lpszUrl);
1608 DWORD offset = (key & (HASHTABLE_NUM_ENTRIES-1)) * HASHTABLE_BLOCKSIZE;
1609 entry_hash_table* pHashEntry, *pHashPrev = NULL;
1610 DWORD id = 0;
1611 DWORD error;
1612
1613 key = ((key >> HASHTABLE_FLAG_BITS) << HASHTABLE_FLAG_BITS) + dwFieldType;
1614
1615 for (pHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1616 pHashEntry; pHashEntry = urlcache_get_hash_table(pHeader, pHashEntry->next))
1617 {
1618 int i;
1619 pHashPrev = pHashEntry;
1620
1621 if (pHashEntry->id != id++)
1622 {
1623 ERR("not right hash table number (%d) expected %d\n", pHashEntry->id, id);
1624 break;
1625 }
1626 /* make sure that it is in fact a hash entry */
1627 if (pHashEntry->header.signature != HASH_SIGNATURE)
1628 {
1629 ERR("not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->header.signature);
1630 break;
1631 }
1632
1633 for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
1634 {
1635 struct hash_entry *pHashElement = &pHashEntry->hash_table[offset + i];
1636 if (pHashElement->key==HASHTABLE_FREE || pHashElement->key==HASHTABLE_DEL) /* if the slot is free */
1637 {
1638 pHashElement->key = key;
1639 pHashElement->offset = dwOffsetEntry;
1640 return ERROR_SUCCESS;
1641 }
1642 }
1643 }
1644 error = urlcache_create_hash_table(pHeader, pHashPrev, &pHashEntry);
1645 if (error != ERROR_SUCCESS)
1646 return error;
1647
1648 pHashEntry->hash_table[offset].key = key;
1649 pHashEntry->hash_table[offset].offset = dwOffsetEntry;
1650 return ERROR_SUCCESS;
1651 }
1652
1653 /***********************************************************************
1654 * urlcache_enum_hash_tables (Internal)
1655 *
1656 * Enumerates the hash tables in a container.
1657 *
1658 * RETURNS
1659 * TRUE if an entry was found
1660 * FALSE if there are no more tables to enumerate.
1661 *
1662 */
1663 static BOOL urlcache_enum_hash_tables(const urlcache_header *pHeader, DWORD *id, entry_hash_table **ppHashEntry)
1664 {
1665 for (*ppHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1666 *ppHashEntry; *ppHashEntry = urlcache_get_hash_table(pHeader, (*ppHashEntry)->next))
1667 {
1668 TRACE("looking at hash table number %d\n", (*ppHashEntry)->id);
1669 if ((*ppHashEntry)->id != *id)
1670 continue;
1671 /* make sure that it is in fact a hash entry */
1672 if ((*ppHashEntry)->header.signature != HASH_SIGNATURE)
1673 {
1674 ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&(*ppHashEntry)->header.signature);
1675 (*id)++;
1676 continue;
1677 }
1678
1679 TRACE("hash table number %d found\n", *id);
1680 return TRUE;
1681 }
1682 return FALSE;
1683 }
1684
1685 /***********************************************************************
1686 * urlcache_enum_hash_table_entries (Internal)
1687 *
1688 * Enumerates entries in a hash table and returns the next non-free entry.
1689 *
1690 * RETURNS
1691 * TRUE if an entry was found
1692 * FALSE if the hash table is empty or there are no more entries to
1693 * enumerate.
1694 *
1695 */
1696 static BOOL urlcache_enum_hash_table_entries(const urlcache_header *pHeader, const entry_hash_table *pHashEntry,
1697 DWORD * index, const struct hash_entry **ppHashEntry)
1698 {
1699 for (; *index < HASHTABLE_SIZE ; (*index)++)
1700 {
1701 if (pHashEntry->hash_table[*index].key==HASHTABLE_FREE || pHashEntry->hash_table[*index].key==HASHTABLE_DEL)
1702 continue;
1703
1704 *ppHashEntry = &pHashEntry->hash_table[*index];
1705 TRACE("entry found %d\n", *index);
1706 return TRUE;
1707 }
1708 TRACE("no more entries (%d)\n", *index);
1709 return FALSE;
1710 }
1711
1712 /***********************************************************************
1713 * cache_container_delete_dir (Internal)
1714 *
1715 * Erase a directory containing an URL cache.
1716 *
1717 * RETURNS
1718 * TRUE success, FALSE failure/aborted.
1719 *
1720 */
1721 static BOOL cache_container_delete_dir(LPCWSTR lpszPath)
1722 {
1723 DWORD path_len;
1724 WCHAR path[MAX_PATH + 1];
1725 SHFILEOPSTRUCTW shfos;
1726 int ret;
1727
1728 path_len = strlenW(lpszPath);
1729 if (path_len >= MAX_PATH)
1730 return FALSE;
1731 strcpyW(path, lpszPath);
1732 path[path_len + 1] = 0; /* double-NUL-terminate path */
1733
1734 shfos.hwnd = NULL;
1735 shfos.wFunc = FO_DELETE;
1736 shfos.pFrom = path;
1737 shfos.pTo = NULL;
1738 shfos.fFlags = FOF_NOCONFIRMATION;
1739 shfos.fAnyOperationsAborted = FALSE;
1740 ret = SHFileOperationW(&shfos);
1741 if (ret)
1742 ERR("SHFileOperationW on %s returned %i\n", debugstr_w(path), ret);
1743 return !(ret || shfos.fAnyOperationsAborted);
1744 }
1745
1746 /***********************************************************************
1747 * urlcache_hash_entry_is_locked (Internal)
1748 *
1749 * Checks if entry is locked. Unlocks it if possible.
1750 */
1751 static BOOL urlcache_hash_entry_is_locked(struct hash_entry *hash_entry, entry_url *url_entry)
1752 {
1753 FILETIME cur_time;
1754 ULARGE_INTEGER acc_time, time;
1755
1756 if ((hash_entry->key & ((1<<HASHTABLE_FLAG_BITS)-1)) != HASHTABLE_LOCK)
1757 return FALSE;
1758
1759 GetSystemTimeAsFileTime(&cur_time);
1760 time.u.LowPart = cur_time.dwLowDateTime;
1761 time.u.HighPart = cur_time.dwHighDateTime;
1762
1763 acc_time.u.LowPart = url_entry->access_time.dwLowDateTime;
1764 acc_time.u.HighPart = url_entry->access_time.dwHighDateTime;
1765
1766 time.QuadPart -= acc_time.QuadPart;
1767
1768 /* check if entry was locked for at least a day */
1769 if(time.QuadPart > (ULONGLONG)24*60*60*FILETIME_SECOND) {
1770 urlcache_hash_entry_set_flags(hash_entry, HASHTABLE_URL);
1771 url_entry->use_count = 0;
1772 return FALSE;
1773 }
1774
1775 return TRUE;
1776 }
1777
1778 static BOOL urlcache_get_entry_info(const char *url, void *entry_info,
1779 DWORD *size, DWORD flags, BOOL unicode)
1780 {
1781 urlcache_header *header;
1782 struct hash_entry *hash_entry;
1783 const entry_url *url_entry;
1784 cache_container *container;
1785 DWORD error;
1786
1787 TRACE("(%s, %p, %p, %x, %x)\n", debugstr_a(url), entry_info, size, flags, unicode);
1788
1789 if(flags & ~GET_INSTALLED_ENTRY)
1790 FIXME("ignoring unsupported flags: %x\n", flags);
1791
1792 error = cache_containers_find(url, &container);
1793 if(error != ERROR_SUCCESS) {
1794 SetLastError(error);
1795 return FALSE;
1796 }
1797
1798 error = cache_container_open_index(container, MIN_BLOCK_NO);
1799 if(error != ERROR_SUCCESS) {
1800 SetLastError(error);
1801 return FALSE;
1802 }
1803
1804 if(!(header = cache_container_lock_index(container)))
1805 return FALSE;
1806
1807 if(!urlcache_find_hash_entry(header, url, &hash_entry)) {
1808 cache_container_unlock_index(container, header);
1809 WARN("entry %s not found!\n", debugstr_a(url));
1810 SetLastError(ERROR_FILE_NOT_FOUND);
1811 return FALSE;
1812 }
1813
1814 url_entry = (const entry_url*)((LPBYTE)header + hash_entry->offset);
1815 if(url_entry->header.signature != URL_SIGNATURE) {
1816 cache_container_unlock_index(container, header);
1817 FIXME("Trying to retrieve entry of unknown format %s\n",
1818 debugstr_an((LPCSTR)&url_entry->header.signature, sizeof(DWORD)));
1819 SetLastError(ERROR_FILE_NOT_FOUND);
1820 return FALSE;
1821 }
1822
1823 TRACE("Found URL: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->url_off));
1824 TRACE("Header info: %s\n", debugstr_an((LPCSTR)url_entry +
1825 url_entry->header_info_off, url_entry->header_info_size));
1826
1827 if((flags & GET_INSTALLED_ENTRY) && !(url_entry->cache_entry_type & INSTALLED_CACHE_ENTRY)) {
1828 cache_container_unlock_index(container, header);
1829 SetLastError(ERROR_FILE_NOT_FOUND);
1830 return FALSE;
1831 }
1832
1833 if(size) {
1834 if(!entry_info)
1835 *size = 0;
1836
1837 error = urlcache_copy_entry(container, header, entry_info, size, url_entry, unicode);
1838 if(error != ERROR_SUCCESS) {
1839 cache_container_unlock_index(container, header);
1840 SetLastError(error);
1841 return FALSE;
1842 }
1843 if(url_entry->local_name_off)
1844 TRACE("Local File Name: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->local_name_off));
1845 }
1846
1847 cache_container_unlock_index(container, header);
1848 return TRUE;
1849 }
1850
1851 /***********************************************************************
1852 * GetUrlCacheEntryInfoExA (WININET.@)
1853 *
1854 */
1855 BOOL WINAPI GetUrlCacheEntryInfoExA(LPCSTR lpszUrl,
1856 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1857 LPDWORD lpdwCacheEntryInfoBufSize, LPSTR lpszReserved,
1858 LPDWORD lpdwReserved, LPVOID lpReserved, DWORD dwFlags)
1859 {
1860 if(lpszReserved!=NULL || lpdwReserved!=NULL || lpReserved!=NULL) {
1861 ERR("Reserved value was not 0\n");
1862 SetLastError(ERROR_INVALID_PARAMETER);
1863 return FALSE;
1864 }
1865
1866 return urlcache_get_entry_info(lpszUrl, lpCacheEntryInfo,
1867 lpdwCacheEntryInfoBufSize, dwFlags, FALSE);
1868 }
1869
1870 /***********************************************************************
1871 * GetUrlCacheEntryInfoA (WININET.@)
1872 *
1873 */
1874 BOOL WINAPI GetUrlCacheEntryInfoA(LPCSTR lpszUrlName,
1875 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1876 LPDWORD lpdwCacheEntryInfoBufferSize)
1877 {
1878 return GetUrlCacheEntryInfoExA(lpszUrlName, lpCacheEntryInfo,
1879 lpdwCacheEntryInfoBufferSize, NULL, NULL, NULL, 0);
1880 }
1881
1882 static int urlcache_encode_url(const WCHAR *url, char *encoded_url, int encoded_len)
1883 {
1884 URL_COMPONENTSW uc;
1885 DWORD len, part_len;
1886 WCHAR *punycode;
1887
1888 TRACE("%s\n", debugstr_w(url));
1889
1890 memset(&uc, 0, sizeof(uc));
1891 uc.dwStructSize = sizeof(uc);
1892 uc.dwHostNameLength = 1;
1893 if(!InternetCrackUrlW(url, 0, 0, &uc))
1894 uc.nScheme = INTERNET_SCHEME_UNKNOWN;
1895
1896 if(uc.nScheme!=INTERNET_SCHEME_HTTP && uc.nScheme!=INTERNET_SCHEME_HTTPS)
1897 return WideCharToMultiByte(CP_UTF8, 0, url, -1, encoded_url, encoded_len, NULL, NULL);
1898
1899 len = WideCharToMultiByte(CP_UTF8, 0, url, uc.lpszHostName-url,
1900 encoded_url, encoded_len, NULL, NULL);
1901 if(!len)
1902 return 0;
1903 if(encoded_url)
1904 encoded_len -= len;
1905
1906 part_len = IdnToAscii(0, uc.lpszHostName, uc.dwHostNameLength, NULL, 0);
1907 if(!part_len) {
1908 SetLastError(ERROR_INTERNET_INVALID_URL);
1909 return 0;
1910 }
1911
1912 punycode = heap_alloc(part_len*sizeof(WCHAR));
1913 if(!punycode)
1914 return 0;
1915
1916 part_len = IdnToAscii(0, uc.lpszHostName, uc.dwHostNameLength, punycode, part_len);
1917 if(!part_len) {
1918 heap_free(punycode);
1919 return 0;
1920 }
1921
1922 part_len = WideCharToMultiByte(CP_UTF8, 0, punycode, part_len,
1923 encoded_url ? encoded_url+len : NULL, encoded_len, NULL, NULL);
1924 heap_free(punycode);
1925 if(!part_len)
1926 return 0;
1927 if(encoded_url)
1928 encoded_len -= part_len;
1929 len += part_len;
1930
1931 part_len = WideCharToMultiByte(CP_UTF8, 0, uc.lpszHostName+uc.dwHostNameLength,
1932 -1, encoded_url ? encoded_url+len : NULL, encoded_len, NULL, NULL);
1933 if(!part_len)
1934 return 0;
1935 len += part_len;
1936
1937 TRACE("got (%d)%s\n", len, debugstr_a(encoded_url));
1938 return len;
1939 }
1940
1941 static BOOL urlcache_encode_url_alloc(const WCHAR *url, char **encoded_url)
1942 {
1943 DWORD encoded_len;
1944 char *ret;
1945
1946 encoded_len = urlcache_encode_url(url, NULL, 0);
1947 if(!encoded_len)
1948 return FALSE;
1949
1950 ret = heap_alloc(encoded_len*sizeof(WCHAR));
1951 if(!ret)
1952 return FALSE;
1953
1954 encoded_len = urlcache_encode_url(url, ret, encoded_len);
1955 if(!encoded_len) {
1956 heap_free(ret);
1957 return FALSE;
1958 }
1959
1960 *encoded_url = ret;
1961 return TRUE;
1962 }
1963
1964 /***********************************************************************
1965 * GetUrlCacheEntryInfoExW (WININET.@)
1966 *
1967 */
1968 BOOL WINAPI GetUrlCacheEntryInfoExW(LPCWSTR lpszUrl,
1969 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
1970 LPDWORD lpdwCacheEntryInfoBufSize, LPWSTR lpszReserved,
1971 LPDWORD lpdwReserved, LPVOID lpReserved, DWORD dwFlags)
1972 {
1973 char *url;
1974 BOOL ret;
1975
1976 if(lpszReserved!=NULL || lpdwReserved!=NULL || lpReserved!=NULL) {
1977 ERR("Reserved value was not 0\n");
1978 SetLastError(ERROR_INVALID_PARAMETER);
1979 return FALSE;
1980 }
1981
1982 /* Ignore GET_INSTALLED_ENTRY flag in unicode version of function */
1983 dwFlags &= ~GET_INSTALLED_ENTRY;
1984
1985 if(!urlcache_encode_url_alloc(lpszUrl, &url))
1986 return FALSE;
1987
1988 ret = urlcache_get_entry_info(url, lpCacheEntryInfo,
1989 lpdwCacheEntryInfoBufSize, dwFlags, TRUE);
1990 heap_free(url);
1991 return ret;
1992 }
1993
1994 /***********************************************************************
1995 * GetUrlCacheEntryInfoW (WININET.@)
1996 *
1997 */
1998 BOOL WINAPI GetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
1999 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2000 LPDWORD lpdwCacheEntryInfoBufferSize)
2001 {
2002 return GetUrlCacheEntryInfoExW(lpszUrl, lpCacheEntryInfo,
2003 lpdwCacheEntryInfoBufferSize, NULL, NULL, NULL, 0);
2004 }
2005
2006 /***********************************************************************
2007 * SetUrlCacheEntryInfoA (WININET.@)
2008 */
2009 BOOL WINAPI SetUrlCacheEntryInfoA(LPCSTR lpszUrlName,
2010 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
2011 DWORD dwFieldControl)
2012 {
2013 urlcache_header *pHeader;
2014 struct hash_entry *pHashEntry;
2015 entry_header *pEntry;
2016 cache_container *pContainer;
2017 DWORD error;
2018
2019 TRACE("(%s, %p, 0x%08x)\n", debugstr_a(lpszUrlName), lpCacheEntryInfo, dwFieldControl);
2020
2021 error = cache_containers_find(lpszUrlName, &pContainer);
2022 if (error != ERROR_SUCCESS)
2023 {
2024 SetLastError(error);
2025 return FALSE;
2026 }
2027
2028 error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
2029 if (error != ERROR_SUCCESS)
2030 {
2031 SetLastError(error);
2032 return FALSE;
2033 }
2034
2035 if (!(pHeader = cache_container_lock_index(pContainer)))
2036 return FALSE;
2037
2038 if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
2039 {
2040 cache_container_unlock_index(pContainer, pHeader);
2041 WARN("entry %s not found!\n", debugstr_a(lpszUrlName));
2042 SetLastError(ERROR_FILE_NOT_FOUND);
2043 return FALSE;
2044 }
2045
2046 pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2047 if (pEntry->signature != URL_SIGNATURE)
2048 {
2049 cache_container_unlock_index(pContainer, pHeader);
2050 FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->signature, sizeof(DWORD)));
2051 SetLastError(ERROR_FILE_NOT_FOUND);
2052 return FALSE;
2053 }
2054
2055 urlcache_set_entry_info((entry_url*)pEntry, lpCacheEntryInfo, dwFieldControl);
2056
2057 cache_container_unlock_index(pContainer, pHeader);
2058
2059 return TRUE;
2060 }
2061
2062 /***********************************************************************
2063 * SetUrlCacheEntryInfoW (WININET.@)
2064 */
2065 BOOL WINAPI SetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
2066 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2067 DWORD dwFieldControl)
2068 {
2069 char *url;
2070 BOOL ret;
2071
2072 if(!urlcache_encode_url_alloc(lpszUrl, &url))
2073 return FALSE;
2074
2075 ret = SetUrlCacheEntryInfoA(url, (INTERNET_CACHE_ENTRY_INFOA*)lpCacheEntryInfo, dwFieldControl);
2076 heap_free(url);
2077 return ret;
2078 }
2079
2080 static BOOL urlcache_entry_get_file(const char *url, void *entry_info, DWORD *size, BOOL unicode)
2081 {
2082 urlcache_header *header;
2083 struct hash_entry *hash_entry;
2084 entry_url *url_entry;
2085 cache_container *container;
2086 DWORD error;
2087
2088 TRACE("(%s, %p, %p, %x)\n", debugstr_a(url), entry_info, size, unicode);
2089
2090 if(!url || !size || (!entry_info && *size)) {
2091 SetLastError(ERROR_INVALID_PARAMETER);
2092 return FALSE;
2093 }
2094
2095 error = cache_containers_find(url, &container);
2096 if(error != ERROR_SUCCESS) {
2097 SetLastError(error);
2098 return FALSE;
2099 }
2100
2101 error = cache_container_open_index(container, MIN_BLOCK_NO);
2102 if (error != ERROR_SUCCESS) {
2103 SetLastError(error);
2104 return FALSE;
2105 }
2106
2107 if (!(header = cache_container_lock_index(container)))
2108 return FALSE;
2109
2110 if (!urlcache_find_hash_entry(header, url, &hash_entry)) {
2111 cache_container_unlock_index(container, header);
2112 TRACE("entry %s not found!\n", url);
2113 SetLastError(ERROR_FILE_NOT_FOUND);
2114 return FALSE;
2115 }
2116
2117 url_entry = (entry_url*)((LPBYTE)header + hash_entry->offset);
2118 if(url_entry->header.signature != URL_SIGNATURE) {
2119 cache_container_unlock_index(container, header);
2120 FIXME("Trying to retrieve entry of unknown format %s\n",
2121 debugstr_an((LPSTR)&url_entry->header.signature, sizeof(DWORD)));
2122 SetLastError(ERROR_FILE_NOT_FOUND);
2123 return FALSE;
2124 }
2125
2126 if(!url_entry->local_name_off) {
2127 cache_container_unlock_index(container, header);
2128 SetLastError(ERROR_INVALID_DATA);
2129 return FALSE;
2130 }
2131
2132 TRACE("Found URL: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->url_off));
2133 TRACE("Header info: %s\n", debugstr_an((LPCSTR)url_entry + url_entry->header_info_off,
2134 url_entry->header_info_size));
2135
2136 error = urlcache_copy_entry(container, header, entry_info,
2137 size, url_entry, unicode);
2138 if(error != ERROR_SUCCESS) {
2139 cache_container_unlock_index(container, header);
2140 SetLastError(error);
2141 return FALSE;
2142 }
2143 TRACE("Local File Name: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->local_name_off));
2144
2145 url_entry->hit_rate++;
2146 url_entry->use_count++;
2147 urlcache_hash_entry_set_flags(hash_entry, HASHTABLE_LOCK);
2148 GetSystemTimeAsFileTime(&url_entry->access_time);
2149
2150 cache_container_unlock_index(container, header);
2151
2152 return TRUE;
2153 }
2154
2155 /***********************************************************************
2156 * RetrieveUrlCacheEntryFileA (WININET.@)
2157 *
2158 */
2159 BOOL WINAPI RetrieveUrlCacheEntryFileA(LPCSTR lpszUrlName,
2160 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
2161 LPDWORD lpdwCacheEntryInfoBufferSize, DWORD dwReserved)
2162 {
2163 return urlcache_entry_get_file(lpszUrlName, lpCacheEntryInfo,
2164 lpdwCacheEntryInfoBufferSize, FALSE);
2165 }
2166
2167 /***********************************************************************
2168 * RetrieveUrlCacheEntryFileW (WININET.@)
2169 *
2170 */
2171 BOOL WINAPI RetrieveUrlCacheEntryFileW(LPCWSTR lpszUrlName,
2172 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2173 LPDWORD lpdwCacheEntryInfoBufferSize, DWORD dwReserved)
2174 {
2175 char *url;
2176 BOOL ret;
2177
2178 if(!urlcache_encode_url_alloc(lpszUrlName, &url))
2179 return FALSE;
2180
2181 ret = urlcache_entry_get_file(url, lpCacheEntryInfo,
2182 lpdwCacheEntryInfoBufferSize, TRUE);
2183 heap_free(url);
2184 return ret;
2185 }
2186
2187 static BOOL urlcache_entry_delete(const cache_container *pContainer,
2188 urlcache_header *pHeader, struct hash_entry *pHashEntry)
2189 {
2190 entry_header *pEntry;
2191 entry_url * pUrlEntry;
2192
2193 pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2194 if (pEntry->signature != URL_SIGNATURE)
2195 {
2196 FIXME("Trying to delete entry of unknown format %s\n",
2197 debugstr_an((LPCSTR)&pEntry->signature, sizeof(DWORD)));
2198 SetLastError(ERROR_FILE_NOT_FOUND);
2199 return FALSE;
2200 }
2201
2202 pUrlEntry = (entry_url *)pEntry;
2203 if(urlcache_hash_entry_is_locked(pHashEntry, pUrlEntry))
2204 {
2205 TRACE("Trying to delete locked entry\n");
2206 pUrlEntry->cache_entry_type |= PENDING_DELETE_CACHE_ENTRY;
2207 SetLastError(ERROR_SHARING_VIOLATION);
2208 return FALSE;
2209 }
2210
2211 if(!urlcache_delete_file(pContainer, pHeader, pUrlEntry))
2212 {
2213 urlcache_entry_free(pHeader, pEntry);
2214 }
2215 else
2216 {
2217 /* Add entry to leaked files list */
2218 pUrlEntry->header.signature = LEAK_SIGNATURE;
2219 pUrlEntry->exempt_delta = pHeader->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET];
2220 pHeader->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET] = pHashEntry->offset;
2221 }
2222
2223 urlcache_hash_entry_delete(pHashEntry);
2224 return TRUE;
2225 }
2226
2227 static HANDLE free_cache_running;
2228 static HANDLE dll_unload_event;
2229 static DWORD WINAPI handle_full_cache_worker(void *param)
2230 {
2231 FreeUrlCacheSpaceW(NULL, 20, 0);
2232 ReleaseSemaphore(free_cache_running, 1, NULL);
2233 return 0;
2234 }
2235
2236 static void handle_full_cache(void)
2237 {
2238 if(WaitForSingleObject(free_cache_running, 0) == WAIT_OBJECT_0) {
2239 if(!QueueUserWorkItem(handle_full_cache_worker, NULL, 0))
2240 ReleaseSemaphore(free_cache_running, 1, NULL);
2241 }
2242 }
2243
2244 /* Enumerates entries in cache, allows cache unlocking between calls. */
2245 static BOOL urlcache_next_entry(urlcache_header *header, DWORD *hash_table_off, DWORD *hash_table_entry,
2246 struct hash_entry **hash_entry, entry_header **entry)
2247 {
2248 entry_hash_table *hashtable_entry;
2249
2250 *hash_entry = NULL;
2251 *entry = NULL;
2252
2253 if(!*hash_table_off) {
2254 *hash_table_off = header->hash_table_off;
2255 *hash_table_entry = 0;
2256
2257 hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2258 }else {
2259 if(*hash_table_off >= header->size) {
2260 *hash_table_off = 0;
2261 return FALSE;
2262 }
2263
2264 hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2265 }
2266
2267 if(hashtable_entry->header.signature != HASH_SIGNATURE) {
2268 *hash_table_off = 0;
2269 return FALSE;
2270 }
2271
2272 while(1) {
2273 if(*hash_table_entry >= HASHTABLE_SIZE) {
2274 *hash_table_off = hashtable_entry->next;
2275 if(!*hash_table_off) {
2276 *hash_table_off = 0;
2277 return FALSE;
2278 }
2279
2280 hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2281 *hash_table_entry = 0;
2282 }
2283
2284 if(hashtable_entry->hash_table[*hash_table_entry].key != HASHTABLE_DEL &&
2285 hashtable_entry->hash_table[*hash_table_entry].key != HASHTABLE_FREE) {
2286 *hash_entry = &hashtable_entry->hash_table[*hash_table_entry];
2287 *entry = (entry_header*)((LPBYTE)header + hashtable_entry->hash_table[*hash_table_entry].offset);
2288 (*hash_table_entry)++;
2289 return TRUE;
2290 }
2291
2292 (*hash_table_entry)++;
2293 }
2294
2295 *hash_table_off = 0;
2296 return FALSE;
2297 }
2298
2299 /* Rates an urlcache entry to determine if it can be deleted.
2300 *
2301 * Score 0 means that entry can safely be removed, the bigger rating
2302 * the smaller chance of entry being removed.
2303 * DWORD_MAX means that entry can't be deleted at all.
2304 *
2305 * Rating system is currently not fully compatible with native implementation.
2306 */
2307 static DWORD urlcache_rate_entry(entry_url *url_entry, FILETIME *cur_time)
2308 {
2309 ULARGE_INTEGER time, access_time;
2310 DWORD rating;
2311
2312 access_time.u.LowPart = url_entry->access_time.dwLowDateTime;
2313 access_time.u.HighPart = url_entry->access_time.dwHighDateTime;
2314
2315 time.u.LowPart = cur_time->dwLowDateTime;
2316 time.u.HighPart = cur_time->dwHighDateTime;
2317
2318 /* Don't touch entries that were added less than 10 minutes ago */
2319 if(time.QuadPart < access_time.QuadPart + (ULONGLONG)10*60*FILETIME_SECOND)
2320 return -1;
2321
2322 if(url_entry->cache_entry_type & STICKY_CACHE_ENTRY)
2323 if(time.QuadPart < access_time.QuadPart + (ULONGLONG)url_entry->exempt_delta*FILETIME_SECOND)
2324 return -1;
2325
2326 time.QuadPart = (time.QuadPart-access_time.QuadPart)/FILETIME_SECOND;
2327 rating = 400*60*60*24/(60*60*24+time.QuadPart);
2328
2329 if(url_entry->hit_rate > 100)
2330 rating += 100;
2331 else
2332 rating += url_entry->hit_rate;
2333
2334 return rating;
2335 }
2336
2337 static int dword_cmp(const void *p1, const void *p2)
2338 {
2339 return *(const DWORD*)p1 - *(const DWORD*)p2;
2340 }
2341
2342 /***********************************************************************
2343 * FreeUrlCacheSpaceW (WININET.@)
2344 *
2345 * Frees up some cache.
2346 *
2347 * PARAMETERS
2348 * cache_path [I] Which volume to free up from, or NULL if you don't care.
2349 * size [I] Percentage of the cache that should be free.
2350 * filter [I] Which entries can't be deleted (CacheEntryType)
2351 *
2352 * RETURNS
2353 * TRUE success. FALSE failure.
2354 *
2355 * IMPLEMENTATION
2356 * This implementation just retrieves the path of the cache directory, and
2357 * deletes its contents from the filesystem. The correct approach would
2358 * probably be to implement and use {FindFirst,FindNext,Delete}UrlCacheGroup().
2359 */
2360 BOOL WINAPI FreeUrlCacheSpaceW(LPCWSTR cache_path, DWORD size, DWORD filter)
2361 {
2362 cache_container *container;
2363 DWORD path_len, err;
2364
2365 TRACE("(%s, %x, %x)\n", debugstr_w(cache_path), size, filter);
2366
2367 if(size<1 || size>100) {
2368 SetLastError(ERROR_INVALID_PARAMETER);
2369 return FALSE;
2370 }
2371
2372 if(cache_path) {
2373 path_len = strlenW(cache_path);
2374 if(cache_path[path_len-1] == '\\')
2375 path_len--;
2376 }else {
2377 path_len = 0;
2378 }
2379
2380 if(size==100 && !filter) {
2381 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
2382 {
2383 /* When cache_path==NULL only clean Temporary Internet Files */
2384 if((!path_len && container->cache_prefix[0]==0) ||
2385 (path_len && !strncmpiW(container->path, cache_path, path_len) &&
2386 (container->path[path_len]=='\0' || container->path[path_len]=='\\')))
2387 {
2388 BOOL ret_del;
2389
2390 WaitForSingleObject(container->mutex, INFINITE);
2391
2392 /* unlock, delete, recreate and lock cache */
2393 cache_container_close_index(container);
2394 ret_del = cache_container_delete_dir(container->path);
2395 err = cache_container_open_index(container, MIN_BLOCK_NO);
2396
2397 ReleaseMutex(container->mutex);
2398 if(!ret_del || (err != ERROR_SUCCESS))
2399 return FALSE;
2400 }
2401 }
2402
2403 return TRUE;
2404 }
2405
2406 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
2407 {
2408 urlcache_header *header;
2409 struct hash_entry *hash_entry;
2410 entry_header *entry;
2411 entry_url *url_entry;
2412 ULONGLONG desired_size, cur_size;
2413 DWORD delete_factor, hash_table_off, hash_table_entry;
2414 DWORD rate[100], rate_no;
2415 FILETIME cur_time;
2416
2417 if((path_len || container->cache_prefix[0]!=0) &&
2418 (!path_len || strncmpiW(container->path, cache_path, path_len) ||
2419 (container->path[path_len]!='\0' && container->path[path_len]!='\\')))
2420 continue;
2421
2422 err = cache_container_open_index(container, MIN_BLOCK_NO);
2423 if(err != ERROR_SUCCESS)
2424 continue;
2425
2426 header = cache_container_lock_index(container);
2427 if(!header)
2428 continue;
2429
2430 urlcache_clean_leaked_entries(container, header);
2431
2432 desired_size = header->cache_limit.QuadPart*(100-size)/100;
2433 cur_size = header->cache_usage.QuadPart+header->exempt_usage.QuadPart;
2434 if(cur_size <= desired_size)
2435 delete_factor = 0;
2436 else
2437 delete_factor = (cur_size-desired_size)*100/cur_size;
2438
2439 if(!delete_factor) {
2440 cache_container_unlock_index(container, header);
2441 continue;
2442 }
2443
2444 hash_table_off = 0;
2445 hash_table_entry = 0;
2446 rate_no = 0;
2447 GetSystemTimeAsFileTime(&cur_time);
2448 while(rate_no<sizeof(rate)/sizeof(*rate) &&
2449 urlcache_next_entry(header, &hash_table_off, &hash_table_entry, &hash_entry, &entry)) {
2450 if(entry->signature != URL_SIGNATURE) {
2451 WARN("only url entries are currently supported\n");
2452 continue;
2453 }
2454
2455 url_entry = (entry_url*)entry;
2456 if(url_entry->cache_entry_type & filter)
2457 continue;
2458
2459 rate[rate_no] = urlcache_rate_entry(url_entry, &cur_time);
2460 if(rate[rate_no] != -1)
2461 rate_no++;
2462 }
2463
2464 if(!rate_no) {
2465 TRACE("nothing to delete\n");
2466 cache_container_unlock_index(container, header);
2467 continue;
2468 }
2469
2470 qsort(rate, rate_no, sizeof(DWORD), dword_cmp);
2471
2472 delete_factor = delete_factor*rate_no/100;
2473 delete_factor = rate[delete_factor];
2474 TRACE("deleting files with rating %d or less\n", delete_factor);
2475
2476 hash_table_off = 0;
2477 while(urlcache_next_entry(header, &hash_table_off, &hash_table_entry, &hash_entry, &entry)) {
2478 if(entry->signature != URL_SIGNATURE)
2479 continue;
2480
2481 url_entry = (entry_url*)entry;
2482 if(url_entry->cache_entry_type & filter)
2483 continue;
2484
2485 if(urlcache_rate_entry(url_entry, &cur_time) <= delete_factor) {
2486 TRACE("deleting file: %s\n", debugstr_a((char*)url_entry+url_entry->local_name_off));
2487 urlcache_entry_delete(container, header, hash_entry);
2488
2489 if(header->cache_usage.QuadPart+header->exempt_usage.QuadPart <= desired_size)
2490 break;
2491
2492 /* Allow other threads to use cache while cleaning */
2493 cache_container_unlock_index(container, header);
2494 if(WaitForSingleObject(dll_unload_event, 0) == WAIT_OBJECT_0) {
2495 TRACE("got dll_unload_event - finishing\n");
2496 return TRUE;
2497 }
2498 Sleep(0);
2499 header = cache_container_lock_index(container);
2500 }
2501 }
2502
2503 TRACE("cache size after cleaning 0x%s/0x%s\n",
2504 wine_dbgstr_longlong(header->cache_usage.QuadPart+header->exempt_usage.QuadPart),
2505 wine_dbgstr_longlong(header->cache_limit.QuadPart));
2506 cache_container_unlock_index(container, header);
2507 }
2508
2509 return TRUE;
2510 }
2511
2512 /***********************************************************************
2513 * FreeUrlCacheSpaceA (WININET.@)
2514 *
2515 * See FreeUrlCacheSpaceW.
2516 */
2517 BOOL WINAPI FreeUrlCacheSpaceA(LPCSTR lpszCachePath, DWORD dwSize, DWORD dwFilter)
2518 {
2519 BOOL ret = FALSE;
2520 LPWSTR path = heap_strdupAtoW(lpszCachePath);
2521 if (lpszCachePath == NULL || path != NULL)
2522 ret = FreeUrlCacheSpaceW(path, dwSize, dwFilter);
2523 heap_free(path);
2524 return ret;
2525 }
2526
2527 /***********************************************************************
2528 * UnlockUrlCacheEntryFileA (WININET.@)
2529 *
2530 */
2531 BOOL WINAPI UnlockUrlCacheEntryFileA(LPCSTR lpszUrlName, DWORD dwReserved)
2532 {
2533 urlcache_header *pHeader;
2534 struct hash_entry *pHashEntry;
2535 entry_header *pEntry;
2536 entry_url * pUrlEntry;
2537 cache_container *pContainer;
2538 DWORD error;
2539
2540 TRACE("(%s, 0x%08x)\n", debugstr_a(lpszUrlName), dwReserved);
2541
2542 if (dwReserved)
2543 {
2544 ERR("dwReserved != 0\n");
2545 SetLastError(ERROR_INVALID_PARAMETER);
2546 return FALSE;
2547 }
2548
2549 error = cache_containers_find(lpszUrlName, &pContainer);
2550 if (error != ERROR_SUCCESS)
2551 {
2552 SetLastError(error);
2553 return FALSE;
2554 }
2555
2556 error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
2557 if (error != ERROR_SUCCESS)
2558 {
2559 SetLastError(error);
2560 return FALSE;
2561 }
2562
2563 if (!(pHeader = cache_container_lock_index(pContainer)))
2564 return FALSE;
2565
2566 if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
2567 {
2568 cache_container_unlock_index(pContainer, pHeader);
2569 TRACE("entry %s not found!\n", lpszUrlName);
2570 SetLastError(ERROR_FILE_NOT_FOUND);
2571 return FALSE;
2572 }
2573
2574 pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2575 if (pEntry->signature != URL_SIGNATURE)
2576 {
2577 cache_container_unlock_index(pContainer, pHeader);
2578 FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->signature, sizeof(DWORD)));
2579 SetLastError(ERROR_FILE_NOT_FOUND);
2580 return FALSE;
2581 }
2582
2583 pUrlEntry = (entry_url *)pEntry;
2584
2585 if (pUrlEntry->use_count == 0)
2586 {
2587 cache_container_unlock_index(pContainer, pHeader);
2588 return FALSE;
2589 }
2590 pUrlEntry->use_count--;
2591 if (!pUrlEntry->use_count)
2592 {
2593 urlcache_hash_entry_set_flags(pHashEntry, HASHTABLE_URL);
2594 if (pUrlEntry->cache_entry_type & PENDING_DELETE_CACHE_ENTRY)
2595 urlcache_entry_delete(pContainer, pHeader, pHashEntry);
2596 }
2597
2598 cache_container_unlock_index(pContainer, pHeader);
2599
2600 return TRUE;
2601 }
2602
2603 /***********************************************************************
2604 * UnlockUrlCacheEntryFileW (WININET.@)
2605 *
2606 */
2607 BOOL WINAPI UnlockUrlCacheEntryFileW(LPCWSTR lpszUrlName, DWORD dwReserved)
2608 {
2609 char *url;
2610 BOOL ret;
2611
2612 if(!urlcache_encode_url_alloc(lpszUrlName, &url))
2613 return FALSE;
2614
2615 ret = UnlockUrlCacheEntryFileA(url, dwReserved);
2616 heap_free(url);
2617 return ret;
2618 }
2619
2620 static BOOL urlcache_entry_create(const char *url, const char *ext, WCHAR *full_path)
2621 {
2622 cache_container *container;
2623 urlcache_header *header;
2624 char file_name[MAX_PATH];
2625 WCHAR extW[MAX_PATH];
2626 BYTE cache_dir;
2627 LONG full_path_len, ext_len = 0;
2628 BOOL generate_name = FALSE;
2629 DWORD error;
2630 HANDLE file;
2631 FILETIME ft;
2632 URL_COMPONENTSA uc;
2633 int i;
2634
2635 TRACE("(%s, %s, %p)\n", debugstr_a(url), debugstr_a(ext), full_path);
2636
2637 memset(&uc, 0, sizeof(uc));
2638 uc.dwStructSize = sizeof(uc);
2639 uc.dwUrlPathLength = 1;
2640 uc.dwExtraInfoLength = 1;
2641 if(!InternetCrackUrlA(url, 0, 0, &uc))
2642 uc.dwUrlPathLength = 0;
2643
2644 if(!uc.dwUrlPathLength) {
2645 file_name[0] = 0;
2646 }else {
2647 char *p, *e;
2648
2649 p = e = uc.lpszUrlPath+uc.dwUrlPathLength;
2650 while(p>uc.lpszUrlPath && *(p-1)!='/' && *(p-1)!='\\' && *(p-1)!='.')
2651 p--;
2652 if(p>uc.lpszUrlPath && *(p-1)=='.') {
2653 e = p-1;
2654 while(p>uc.lpszUrlPath && *(p-1)!='/' && *(p-1)!='\\')
2655 p--;
2656 }
2657
2658 if(e-p >= MAX_PATH)
2659 e = p+MAX_PATH-1;
2660 memcpy(file_name, p, e-p);
2661 file_name[e-p] = 0;
2662
2663 for(p=file_name; *p; p++) {
2664 switch(*p) {
2665 case '<': case '>':
2666 case ':': case '"':
2667 case '|': case '?':
2668 case '*':
2669 *p = '_'; break;
2670 default: break;
2671 }
2672 }
2673 }
2674
2675 if(!file_name[0])
2676 generate_name = TRUE;
2677
2678 error = cache_containers_find(url, &container);
2679 if(error != ERROR_SUCCESS) {
2680 SetLastError(error);
2681 return FALSE;
2682 }
2683
2684 error = cache_container_open_index(container, MIN_BLOCK_NO);
2685 if(error != ERROR_SUCCESS) {
2686 SetLastError(error);
2687 return FALSE;
2688 }
2689
2690 if(!(header = cache_container_lock_index(container)))
2691 return FALSE;
2692
2693 if(header->dirs_no)
2694 cache_dir = (BYTE)(rand() % header->dirs_no);
2695 else
2696 cache_dir = CACHE_CONTAINER_NO_SUBDIR;
2697
2698 full_path_len = MAX_PATH * sizeof(WCHAR);
2699 if(!urlcache_create_file_pathW(container, header, file_name, cache_dir, full_path, &full_path_len, TRUE)) {
2700 WARN("Failed to get full path for filename %s, needed %u bytes.\n",
2701 debugstr_a(file_name), full_path_len);
2702 cache_container_unlock_index(container, header);
2703 return FALSE;
2704 }
2705 full_path_len = full_path_len/sizeof(WCHAR) - 1;
2706
2707 cache_container_unlock_index(container, header);
2708
2709 if(ext) {
2710 WCHAR *p;
2711
2712 extW[0] = '.';
2713 ext_len = MultiByteToWideChar(CP_ACP, 0, ext, -1, extW+1, MAX_PATH-1);
2714
2715 for(p=extW; *p; p++) {
2716 switch(*p) {
2717 case '<': case '>':
2718 case ':': case '"':
2719 case '|': case '?':
2720 case '*':
2721 *p = '_'; break;
2722 default: break;
2723 }
2724 }
2725 if(p[-1]==' ' || p[-1]=='.')
2726 p[-1] = '_';
2727 }else {
2728 extW[0] = '\0';
2729 }
2730
2731 if(!generate_name && full_path_len+5+ext_len>=MAX_PATH) { /* strlen("[255]") = 5 */
2732 full_path_len = MAX_PATH-5-ext_len-1;
2733 }
2734
2735 for(i=0; i<255 && !generate_name; i++) {
2736 static const WCHAR format[] = {'[','%','u',']','%','s',0};
2737
2738 wsprintfW(full_path+full_path_len, format, i, extW);
2739
2740 TRACE("Trying: %s\n", debugstr_w(full_path));
2741 file = CreateFileW(full_path, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
2742 if(file != INVALID_HANDLE_VALUE) {
2743 CloseHandle(file);
2744 return TRUE;
2745 }
2746 }
2747
2748 if(full_path_len+8+ext_len >= MAX_PATH)
2749 full_path_len = MAX_PATH-8-ext_len-1;
2750
2751 /* Try to generate random name */
2752 GetSystemTimeAsFileTime(&ft);
2753 strcpyW(full_path+full_path_len+8, extW);
2754
2755 for(i=0; i<255; i++) {
2756 int j;
2757 ULONGLONG n = ft.dwHighDateTime;
2758 n <<= 32;
2759 n += ft.dwLowDateTime;
2760 n ^= (ULONGLONG)i<<48;
2761
2762 for(j=0; j<8; j++) {
2763 int r = (n % 36);
2764 n /= 37;
2765 full_path[full_path_len+j] = (r < 10 ? '0' + r : 'A' + r - 10);
2766 }
2767
2768 TRACE("Trying: %s\n", debugstr_w(full_path));
2769 file = CreateFileW(full_path, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
2770 if(file != INVALID_HANDLE_VALUE) {
2771 CloseHandle(file);
2772 return TRUE;
2773 }
2774 }
2775
2776 WARN("Could not find a unique filename\n");
2777 return FALSE;
2778 }
2779
2780 /***********************************************************************
2781 * CreateUrlCacheEntryA (WININET.@)
2782 *
2783 */
2784 BOOL WINAPI CreateUrlCacheEntryA(LPCSTR lpszUrlName, DWORD dwExpectedFileSize,
2785 LPCSTR lpszFileExtension, LPSTR lpszFileName, DWORD dwReserved)
2786 {
2787 WCHAR file_name[MAX_PATH];
2788
2789 if(dwReserved)
2790 FIXME("dwReserved 0x%08x\n", dwReserved);
2791
2792 if(!urlcache_entry_create(lpszUrlName, lpszFileExtension, file_name))
2793 return FALSE;
2794
2795 if(!WideCharToMultiByte(CP_ACP, 0, file_name, -1, lpszFileName, MAX_PATH, NULL, NULL))
2796 return FALSE;
2797 return TRUE;
2798 }
2799 /***********************************************************************
2800 * CreateUrlCacheEntryW (WININET.@)
2801 *
2802 */
2803 BOOL WINAPI CreateUrlCacheEntryW(LPCWSTR lpszUrlName, DWORD dwExpectedFileSize,
2804 LPCWSTR lpszFileExtension, LPWSTR lpszFileName, DWORD dwReserved)
2805 {
2806 char *url, *ext = NULL;
2807 BOOL ret;
2808
2809 if(dwReserved)
2810 FIXME("dwReserved 0x%08x\n", dwReserved);
2811
2812 if(lpszFileExtension) {
2813 ext = heap_strdupWtoUTF8(lpszFileExtension);
2814 if(!ext)
2815 return FALSE;
2816 }
2817
2818 if(!urlcache_encode_url_alloc(lpszUrlName, &url)) {
2819 heap_free(ext);
2820 return FALSE;
2821 }
2822
2823 ret = urlcache_entry_create(url, ext, lpszFileName);
2824 heap_free(ext);
2825 heap_free(url);
2826 return ret;
2827 }
2828
2829 static BOOL urlcache_entry_commit(const char *url, const WCHAR *file_name,
2830 FILETIME expire_time, FILETIME modify_time, DWORD entry_type,
2831 BYTE *header_info, DWORD header_size, const char *file_ext,
2832 const char *original_url)
2833 {
2834 cache_container *container;
2835 urlcache_header *header;
2836 struct hash_entry *hash_entry;
2837 entry_header *entry;
2838 entry_url *url_entry;
2839 DWORD url_entry_offset;
2840 DWORD size = DWORD_ALIGN(sizeof(*url_entry));
2841 DWORD file_name_off = 0;
2842 DWORD header_info_off = 0;
2843 DWORD file_ext_off = 0;
2844 WIN32_FILE_ATTRIBUTE_DATA file_attr;
2845 LARGE_INTEGER file_size;
2846 BYTE dir_id;
2847 char file_name_no_container[MAX_PATH];
2848 char *local_file_name = 0;
2849 DWORD hit_rate = 0;
2850 DWORD exempt_delta = 0;
2851 DWORD error;
2852
2853 TRACE("(%s, %s, ..., ..., %x, %p, %d, %s, %s)\n", debugstr_a(url), debugstr_w(file_name),
2854 entry_type, header_info, header_size, debugstr_a(file_ext), debugstr_a(original_url));
2855
2856 if(entry_type & STICKY_CACHE_ENTRY && !file_name) {
2857 SetLastError(ERROR_INVALID_PARAMETER);
2858 return FALSE;
2859 }
2860 if(original_url)
2861 WARN(": original_url ignored\n");
2862
2863 memset(&file_attr, 0, sizeof(file_attr));
2864 if(file_name) {
2865 if(!GetFileAttributesExW(file_name, GetFileExInfoStandard, &file_attr))
2866 return FALSE;
2867 }
2868 file_size.u.LowPart = file_attr.nFileSizeLow;
2869 file_size.u.HighPart = file_attr.nFileSizeHigh;
2870
2871 error = cache_containers_find(url, &container);
2872 if(error != ERROR_SUCCESS) {
2873 SetLastError(error);
2874 return FALSE;
2875 }
2876
2877 error = cache_container_open_index(container, MIN_BLOCK_NO);
2878 if(error != ERROR_SUCCESS) {
2879 SetLastError(error);
2880 return FALSE;
2881 }
2882
2883 if(!(header = cache_container_lock_index(container)))
2884 return FALSE;
2885
2886 if(urlcache_find_hash_entry(header, url, &hash_entry)) {
2887 entry_url *url_entry = (entry_url*)((LPBYTE)header + hash_entry->offset);
2888
2889 if(urlcache_hash_entry_is_locked(hash_entry, url_entry)) {
2890 TRACE("Trying to overwrite locked entry\n");
2891 cache_container_unlock_index(container, header);
2892 SetLastError(ERROR_SHARING_VIOLATION);
2893 return FALSE;
2894 }
2895
2896 hit_rate = url_entry->hit_rate;
2897 exempt_delta = url_entry->exempt_delta;
2898 urlcache_entry_delete(container, header, hash_entry);
2899 }
2900
2901 if(header->dirs_no)
2902 dir_id = 0;
2903 else
2904 dir_id = CACHE_CONTAINER_NO_SUBDIR;
2905
2906 if(file_name) {
2907 BOOL bFound = FALSE;
2908
2909 if(strncmpW(file_name, container->path, lstrlenW(container->path))) {
2910 ERR("path %s must begin with cache content path %s\n", debugstr_w(file_name), debugstr_w(container->path));
2911 cache_container_unlock_index(container, header);
2912 SetLastError(ERROR_INVALID_PARAMETER);
2913 return FALSE;
2914 }
2915
2916 /* skip container path prefix */
2917 file_name += lstrlenW(container->path);
2918
2919 WideCharToMultiByte(CP_ACP, 0, file_name, -1, file_name_no_container, MAX_PATH, NULL, NULL);
2920 local_file_name = file_name_no_container;
2921
2922 if(header->dirs_no) {
2923 for(dir_id = 0; dir_id < header->dirs_no; dir_id++) {
2924 if(!strncmp(header->directory_data[dir_id].name, local_file_name, DIR_LENGTH)) {
2925 bFound = TRUE;
2926 break;
2927 }
2928 }
2929
2930 if(!bFound) {
2931 ERR("cache directory not found in path %s\n", debugstr_w(file_name));
2932 cache_container_unlock_index(container, header);
2933 SetLastError(ERROR_INVALID_PARAMETER);
2934 return FALSE;
2935 }
2936
2937 file_name += DIR_LENGTH + 1;
2938 local_file_name += DIR_LENGTH + 1;
2939 }
2940 }
2941
2942 size = DWORD_ALIGN(size + strlen(url) + 1);
2943 if(file_name) {
2944 file_name_off = size;
2945 size = DWORD_ALIGN(size + strlen(local_file_name) + 1);
2946 }
2947 if(header_info && header_size) {
2948 header_info_off = size;
2949 size = DWORD_ALIGN(size + header_size);
2950 }
2951 if(file_ext && (file_ext_off = strlen(file_ext))) {
2952 DWORD len = file_ext_off;
2953
2954 file_ext_off = size;
2955 size = DWORD_ALIGN(size + len + 1);
2956 }
2957
2958 /* round up to next block */
2959 if(size % BLOCKSIZE) {
2960 size -= size % BLOCKSIZE;
2961 size += BLOCKSIZE;
2962 }
2963
2964 error = urlcache_entry_alloc(header, size / BLOCKSIZE, &entry);
2965 while(error == ERROR_HANDLE_DISK_FULL) {
2966 error = cache_container_clean_index(container, &header);
2967 if(error == ERROR_SUCCESS)
2968 error = urlcache_entry_alloc(header, size / BLOCKSIZE, &entry);
2969 }
2970 if(error != ERROR_SUCCESS) {
2971 cache_container_unlock_index(container, header);
2972 SetLastError(error);
2973 return FALSE;
2974 }
2975
2976 /* FindFirstFreeEntry fills in blocks used */
2977 url_entry = (entry_url *)entry;
2978 url_entry_offset = (LPBYTE)url_entry - (LPBYTE)header;
2979 url_entry->header.signature = URL_SIGNATURE;
2980 url_entry->cache_dir = dir_id;
2981 url_entry->cache_entry_type = entry_type | container->default_entry_type;
2982 url_entry->header_info_size = header_size;
2983 if((entry_type & STICKY_CACHE_ENTRY) && !exempt_delta) {
2984 /* Sticky entries have a default exempt time of one day */
2985 exempt_delta = 86400;
2986 }
2987 url_entry->exempt_delta = exempt_delta;
2988 url_entry->hit_rate = hit_rate+1;
2989 url_entry->file_extension_off = file_ext_off;
2990 url_entry->header_info_off = header_info_off;
2991 url_entry->local_name_off = file_name_off;
2992 url_entry->url_off = DWORD_ALIGN(sizeof(*url_entry));
2993 url_entry->size.QuadPart = file_size.QuadPart;
2994 url_entry->use_count = 0;
2995 GetSystemTimeAsFileTime(&url_entry->access_time);
2996 url_entry->modification_time = modify_time;
2997 file_time_to_dos_date_time(&url_entry->access_time, &url_entry->sync_date, &url_entry->sync_time);
2998 file_time_to_dos_date_time(&expire_time, &url_entry->expire_date, &url_entry->expire_time);
2999 file_time_to_dos_date_time(&file_attr.ftLastWriteTime, &url_entry->write_date, &url_entry->write_time);
3000
3001 /*** Unknowns ***/
3002 url_entry->unk1 = 0;
3003 url_entry->unk2 = 0;
3004 url_entry->unk3 = 0x60;
3005 url_entry->unk4 = 0;
3006 url_entry->unk5 = 0x1010;
3007 url_entry->unk7 = 0;
3008 url_entry->unk8 = 0;
3009
3010
3011 strcpy((LPSTR)url_entry + url_entry->url_off, url);
3012 if(file_name_off)
3013 strcpy((LPSTR)((LPBYTE)url_entry + file_name_off), local_file_name);
3014 if(header_info_off)
3015 memcpy((LPBYTE)url_entry + header_info_off, header_info, header_size);
3016 if(file_ext_off)
3017 strcpy((LPSTR)((LPBYTE)url_entry + file_ext_off), file_ext);
3018
3019 error = urlcache_hash_entry_create(header, url, url_entry_offset, HASHTABLE_URL);
3020 while(error == ERROR_HANDLE_DISK_FULL) {
3021 error = cache_container_clean_index(container, &header);
3022 if(error == ERROR_SUCCESS) {
3023 url_entry = (entry_url *)((LPBYTE)header + url_entry_offset);
3024 error = urlcache_hash_entry_create(header, url,
3025 url_entry_offset, HASHTABLE_URL);
3026 }
3027 }
3028 if(error != ERROR_SUCCESS) {
3029 urlcache_entry_free(header, &url_entry->header);
3030 cache_container_unlock_index(container, header);
3031 SetLastError(error);
3032 return FALSE;
3033 }
3034
3035 if(url_entry->cache_dir < header->dirs_no)
3036 header->directory_data[url_entry->cache_dir].files_no++;
3037 if(entry_type & STICKY_CACHE_ENTRY)
3038 header->exempt_usage.QuadPart += file_size.QuadPart;
3039 else