a78093c590896a275216b388ca3d897fe98d310a
[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
809 static void cache_containers_free(void)
810 {
811 while(!list_empty(&UrlContainers))
812 cache_container_delete_container(
813 LIST_ENTRY(list_head(&UrlContainers), cache_container, entry)
814 );
815 }
816
817 static DWORD cache_containers_find(const char *url, cache_container **ret)
818 {
819 cache_container *container;
820
821 TRACE("searching for prefix for URL: %s\n", debugstr_a(url));
822
823 if(!url)
824 return ERROR_INVALID_PARAMETER;
825
826 /* ReactOS r54992 */
827 if (!bDefaultContainersAdded)
828 cache_containers_init();
829
830 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
831 {
832 int prefix_len = strlen(container->cache_prefix);
833
834 if(!strncmp(container->cache_prefix, url, prefix_len)) {
835 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
836 *ret = container;
837 return ERROR_SUCCESS;
838 }
839 }
840
841 ERR("no container found\n");
842 return ERROR_FILE_NOT_FOUND;
843 }
844
845 static BOOL cache_containers_enum(char *search_pattern, DWORD index, cache_container **ret)
846 {
847 DWORD i = 0;
848 cache_container *container;
849
850 TRACE("searching for prefix: %s\n", debugstr_a(search_pattern));
851
852 /* non-NULL search pattern only returns one container ever */
853 if (search_pattern && index > 0)
854 return FALSE;
855
856 /* ReactOS r54992 */
857 if (!bDefaultContainersAdded)
858 cache_containers_init();
859
860 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
861 {
862 if (search_pattern)
863 {
864 if (!strcmp(container->cache_prefix, search_pattern))
865 {
866 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
867 *ret = container;
868 return TRUE;
869 }
870 }
871 else
872 {
873 if (i == index)
874 {
875 TRACE("found container with prefix %s\n", debugstr_a(container->cache_prefix));
876 *ret = container;
877 return TRUE;
878 }
879 }
880 i++;
881 }
882 return FALSE;
883 }
884
885 /***********************************************************************
886 * cache_container_lock_index (Internal)
887 *
888 * Locks the index for system-wide exclusive access.
889 *
890 * RETURNS
891 * Cache file header if successful
892 * NULL if failed and calls SetLastError.
893 */
894 static urlcache_header* cache_container_lock_index(cache_container *pContainer)
895 {
896 BYTE index;
897 LPVOID pIndexData;
898 urlcache_header* pHeader;
899 DWORD error;
900
901 /* acquire mutex */
902 WaitForSingleObject(pContainer->mutex, INFINITE);
903
904 pIndexData = MapViewOfFile(pContainer->mapping, FILE_MAP_WRITE, 0, 0, 0);
905
906 if (!pIndexData)
907 {
908 ReleaseMutex(pContainer->mutex);
909 ERR("Couldn't MapViewOfFile. Error: %d\n", GetLastError());
910 return NULL;
911 }
912 pHeader = (urlcache_header*)pIndexData;
913
914 /* file has grown - we need to remap to prevent us getting
915 * access violations when we try and access beyond the end
916 * of the memory mapped file */
917 if (pHeader->size != pContainer->file_size)
918 {
919 UnmapViewOfFile( pHeader );
920 cache_container_close_index(pContainer);
921 error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
922 if (error != ERROR_SUCCESS)
923 {
924 ReleaseMutex(pContainer->mutex);
925 SetLastError(error);
926 return NULL;
927 }
928 pIndexData = MapViewOfFile(pContainer->mapping, FILE_MAP_WRITE, 0, 0, 0);
929
930 if (!pIndexData)
931 {
932 ReleaseMutex(pContainer->mutex);
933 ERR("Couldn't MapViewOfFile. Error: %d\n", GetLastError());
934 return NULL;
935 }
936 pHeader = (urlcache_header*)pIndexData;
937 }
938
939 TRACE("Signature: %s, file size: %d bytes\n", pHeader->signature, pHeader->size);
940
941 for (index = 0; index < pHeader->dirs_no; index++)
942 {
943 TRACE("Directory[%d] = \"%.8s\"\n", index, pHeader->directory_data[index].name);
944 }
945
946 return pHeader;
947 }
948
949 /***********************************************************************
950 * cache_container_unlock_index (Internal)
951 *
952 */
953 static BOOL cache_container_unlock_index(cache_container *pContainer, urlcache_header *pHeader)
954 {
955 /* release mutex */
956 ReleaseMutex(pContainer->mutex);
957 return UnmapViewOfFile(pHeader);
958 }
959
960 /***********************************************************************
961 * urlcache_create_file_pathW (Internal)
962 *
963 * Copies the full path to the specified buffer given the local file
964 * name and the index of the directory it is in. Always sets value in
965 * lpBufferSize to the required buffer size (in bytes).
966 *
967 * RETURNS
968 * TRUE if the buffer was big enough
969 * FALSE if the buffer was too small
970 *
971 */
972 static BOOL urlcache_create_file_pathW(
973 const cache_container *pContainer,
974 const urlcache_header *pHeader,
975 LPCSTR szLocalFileName,
976 BYTE Directory,
977 LPWSTR wszPath,
978 LPLONG lpBufferSize,
979 BOOL trunc_name)
980 {
981 LONG nRequired;
982 int path_len = strlenW(pContainer->path);
983 int file_name_len = MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, NULL, 0);
984 if (Directory!=CACHE_CONTAINER_NO_SUBDIR && Directory>=pHeader->dirs_no)
985 {
986 *lpBufferSize = 0;
987 return FALSE;
988 }
989
990 nRequired = (path_len + file_name_len) * sizeof(WCHAR);
991 if(Directory != CACHE_CONTAINER_NO_SUBDIR)
992 nRequired += (DIR_LENGTH + 1) * sizeof(WCHAR);
993 if (trunc_name && nRequired >= *lpBufferSize)
994 nRequired = *lpBufferSize;
995 if (nRequired <= *lpBufferSize)
996 {
997 int dir_len;
998
999 memcpy(wszPath, pContainer->path, path_len * sizeof(WCHAR));
1000 if (Directory != CACHE_CONTAINER_NO_SUBDIR)
1001 {
1002 dir_len = MultiByteToWideChar(CP_ACP, 0, pHeader->directory_data[Directory].name, DIR_LENGTH, wszPath + path_len, DIR_LENGTH);
1003 wszPath[dir_len + path_len] = '\\';
1004 dir_len++;
1005 }
1006 else
1007 {
1008 dir_len = 0;
1009 }
1010 MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, wszPath + dir_len + path_len,
1011 *lpBufferSize/sizeof(WCHAR)-dir_len-path_len);
1012 wszPath[*lpBufferSize/sizeof(WCHAR)-1] = 0;
1013 *lpBufferSize = nRequired;
1014 return TRUE;
1015 }
1016 *lpBufferSize = nRequired;
1017 return FALSE;
1018 }
1019
1020 /***********************************************************************
1021 * urlcache_create_file_pathA (Internal)
1022 *
1023 * Copies the full path to the specified buffer given the local file
1024 * name and the index of the directory it is in. Always sets value in
1025 * lpBufferSize to the required buffer size.
1026 *
1027 * RETURNS
1028 * TRUE if the buffer was big enough
1029 * FALSE if the buffer was too small
1030 *
1031 */
1032 static BOOL urlcache_create_file_pathA(
1033 const cache_container *pContainer,
1034 const urlcache_header *pHeader,
1035 LPCSTR szLocalFileName,
1036 BYTE Directory,
1037 LPSTR szPath,
1038 LPLONG lpBufferSize)
1039 {
1040 LONG nRequired;
1041 int path_len, file_name_len, dir_len;
1042
1043 if (Directory!=CACHE_CONTAINER_NO_SUBDIR && Directory>=pHeader->dirs_no)
1044 {
1045 *lpBufferSize = 0;
1046 return FALSE;
1047 }
1048
1049 path_len = WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, NULL, 0, NULL, NULL) - 1;
1050 file_name_len = strlen(szLocalFileName) + 1 /* for nul-terminator */;
1051 if (Directory!=CACHE_CONTAINER_NO_SUBDIR)
1052 dir_len = DIR_LENGTH+1;
1053 else
1054 dir_len = 0;
1055
1056 nRequired = (path_len + dir_len + file_name_len) * sizeof(char);
1057 if (nRequired <= *lpBufferSize)
1058 {
1059 WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, szPath, path_len, NULL, NULL);
1060 if(dir_len) {
1061 memcpy(szPath+path_len, pHeader->directory_data[Directory].name, dir_len-1);
1062 szPath[path_len + dir_len-1] = '\\';
1063 }
1064 memcpy(szPath + path_len + dir_len, szLocalFileName, file_name_len);
1065 *lpBufferSize = nRequired;
1066 return TRUE;
1067 }
1068 *lpBufferSize = nRequired;
1069 return FALSE;
1070 }
1071
1072 /* Just like FileTimeToDosDateTime, except that it also maps the special
1073 * case of a filetime of (0,0) to a DOS date/time of (0,0).
1074 */
1075 static void file_time_to_dos_date_time(const FILETIME *ft, WORD *fatdate,
1076 WORD *fattime)
1077 {
1078 if (!ft->dwLowDateTime && !ft->dwHighDateTime)
1079 *fatdate = *fattime = 0;
1080 else
1081 FileTimeToDosDateTime(ft, fatdate, fattime);
1082 }
1083
1084 /***********************************************************************
1085 * urlcache_delete_file (Internal)
1086 */
1087 static DWORD urlcache_delete_file(const cache_container *container,
1088 urlcache_header *header, entry_url *url_entry)
1089 {
1090 WIN32_FILE_ATTRIBUTE_DATA attr;
1091 WCHAR path[MAX_PATH];
1092 LONG path_size = sizeof(path);
1093 DWORD err;
1094 WORD date, time;
1095
1096 if(!url_entry->local_name_off)
1097 goto succ;
1098
1099 if(!urlcache_create_file_pathW(container, header,
1100 (LPCSTR)url_entry+url_entry->local_name_off,
1101 url_entry->cache_dir, path, &path_size, FALSE))
1102 goto succ;
1103
1104 if(!GetFileAttributesExW(path, GetFileExInfoStandard, &attr))
1105 goto succ;
1106 file_time_to_dos_date_time(&attr.ftLastWriteTime, &date, &time);
1107 if(date != url_entry->write_date || time != url_entry->write_time)
1108 goto succ;
1109
1110 err = (DeleteFileW(path) ? ERROR_SUCCESS : GetLastError());
1111 if(err == ERROR_ACCESS_DENIED || err == ERROR_SHARING_VIOLATION)
1112 return err;
1113
1114 succ:
1115 if (url_entry->cache_dir < header->dirs_no)
1116 {
1117 if (header->directory_data[url_entry->cache_dir].files_no)
1118 header->directory_data[url_entry->cache_dir].files_no--;
1119 }
1120 if (url_entry->cache_entry_type & STICKY_CACHE_ENTRY)
1121 {
1122 if (url_entry->size.QuadPart < header->exempt_usage.QuadPart)
1123 header->exempt_usage.QuadPart -= url_entry->size.QuadPart;
1124 else
1125 header->exempt_usage.QuadPart = 0;
1126 }
1127 else
1128 {
1129 if (url_entry->size.QuadPart < header->cache_usage.QuadPart)
1130 header->cache_usage.QuadPart -= url_entry->size.QuadPart;
1131 else
1132 header->cache_usage.QuadPart = 0;
1133 }
1134
1135 return ERROR_SUCCESS;
1136 }
1137
1138 static BOOL urlcache_clean_leaked_entries(cache_container *container, urlcache_header *header)
1139 {
1140 DWORD *leak_off;
1141 BOOL freed = FALSE;
1142
1143 leak_off = &header->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET];
1144 while(*leak_off) {
1145 entry_url *url_entry = (entry_url*)((LPBYTE)header + *leak_off);
1146
1147 if(SUCCEEDED(urlcache_delete_file(container, header, url_entry))) {
1148 *leak_off = url_entry->exempt_delta;
1149 urlcache_entry_free(header, &url_entry->header);
1150 freed = TRUE;
1151 }else {
1152 leak_off = &url_entry->exempt_delta;
1153 }
1154 }
1155
1156 return freed;
1157 }
1158
1159 /***********************************************************************
1160 * cache_container_clean_index (Internal)
1161 *
1162 * This function is meant to make place in index file by removing leaked
1163 * files entries and resizing the file.
1164 *
1165 * CAUTION: file view may get mapped to new memory
1166 *
1167 * RETURNS
1168 * ERROR_SUCCESS when new memory is available
1169 * error code otherwise
1170 */
1171 static DWORD cache_container_clean_index(cache_container *container, urlcache_header **file_view)
1172 {
1173 urlcache_header *header = *file_view;
1174 DWORD ret;
1175
1176 TRACE("(%s %s)\n", debugstr_a(container->cache_prefix), debugstr_w(container->path));
1177
1178 if(urlcache_clean_leaked_entries(container, header))
1179 return ERROR_SUCCESS;
1180
1181 if(header->size >= ALLOCATION_TABLE_SIZE*8*BLOCKSIZE + ENTRY_START_OFFSET) {
1182 WARN("index file has maximal size\n");
1183 return ERROR_NOT_ENOUGH_MEMORY;
1184 }
1185
1186 cache_container_close_index(container);
1187 ret = cache_container_open_index(container, header->capacity_in_blocks*2);
1188 if(ret != ERROR_SUCCESS)
1189 return ret;
1190 header = MapViewOfFile(container->mapping, FILE_MAP_WRITE, 0, 0, 0);
1191 if(!header)
1192 return GetLastError();
1193
1194 UnmapViewOfFile(*file_view);
1195 *file_view = header;
1196 return ERROR_SUCCESS;
1197 }
1198
1199 /* Just like DosDateTimeToFileTime, except that it also maps the special
1200 * case of a DOS date/time of (0,0) to a filetime of (0,0).
1201 */
1202 static void dos_date_time_to_file_time(WORD fatdate, WORD fattime,
1203 FILETIME *ft)
1204 {
1205 if (!fatdate && !fattime)
1206 ft->dwLowDateTime = ft->dwHighDateTime = 0;
1207 else
1208 DosDateTimeToFileTime(fatdate, fattime, ft);
1209 }
1210
1211 static int urlcache_decode_url(const char *url, WCHAR *decoded_url, int decoded_len)
1212 {
1213 URL_COMPONENTSA uc;
1214 DWORD len, part_len;
1215 WCHAR *host_name;
1216
1217 memset(&uc, 0, sizeof(uc));
1218 uc.dwStructSize = sizeof(uc);
1219 uc.dwHostNameLength = 1;
1220 if(!InternetCrackUrlA(url, 0, 0, &uc))
1221 uc.nScheme = INTERNET_SCHEME_UNKNOWN;
1222
1223 if(uc.nScheme!=INTERNET_SCHEME_HTTP && uc.nScheme!=INTERNET_SCHEME_HTTPS)
1224 return MultiByteToWideChar(CP_UTF8, 0, url, -1, decoded_url, decoded_len);
1225
1226 if(!decoded_url)
1227 decoded_len = 0;
1228
1229 len = MultiByteToWideChar(CP_UTF8, 0, url, uc.lpszHostName-url, decoded_url, decoded_len);
1230 if(!len)
1231 return 0;
1232 if(decoded_url)
1233 decoded_len -= len;
1234
1235 host_name = heap_alloc(uc.dwHostNameLength*sizeof(WCHAR));
1236 if(!host_name)
1237 return 0;
1238 if(!MultiByteToWideChar(CP_UTF8, 0, uc.lpszHostName, uc.dwHostNameLength,
1239 host_name, uc.dwHostNameLength)) {
1240 heap_free(host_name);
1241 return 0;
1242 }
1243 part_len = IdnToUnicode(0, host_name, uc.dwHostNameLength,
1244 decoded_url ? decoded_url+len : NULL, decoded_len);
1245 heap_free(host_name);
1246 if(!part_len) {
1247 SetLastError(ERROR_INTERNET_INVALID_URL);
1248 return 0;
1249 }
1250 len += part_len;
1251 if(decoded_url)
1252 decoded_len -= part_len;
1253
1254 part_len = MultiByteToWideChar(CP_UTF8, 0,
1255 uc.lpszHostName+uc.dwHostNameLength,
1256 -1, decoded_url ? decoded_url+len : NULL, decoded_len);
1257 if(!part_len)
1258 return 0;
1259 len += part_len;
1260
1261 return len;
1262 }
1263
1264 /***********************************************************************
1265 * urlcache_copy_entry (Internal)
1266 *
1267 * Copies an entry from the cache index file to the Win32 structure
1268 *
1269 * RETURNS
1270 * ERROR_SUCCESS if the buffer was big enough
1271 * ERROR_INSUFFICIENT_BUFFER if the buffer was too small
1272 *
1273 */
1274 static DWORD urlcache_copy_entry(cache_container *container, const urlcache_header *header,
1275 INTERNET_CACHE_ENTRY_INFOA *entry_info, DWORD *info_size, const entry_url *url_entry, BOOL unicode)
1276 {
1277 int url_len;
1278 DWORD size = sizeof(*entry_info);
1279
1280 if(*info_size >= size) {
1281 entry_info->lpHeaderInfo = NULL;
1282 entry_info->lpszFileExtension = NULL;
1283 entry_info->lpszLocalFileName = NULL;
1284 entry_info->lpszSourceUrlName = NULL;
1285 entry_info->CacheEntryType = url_entry->cache_entry_type;
1286 entry_info->u.dwExemptDelta = url_entry->exempt_delta;
1287 entry_info->dwHeaderInfoSize = url_entry->header_info_size;
1288 entry_info->dwHitRate = url_entry->hit_rate;
1289 entry_info->dwSizeHigh = url_entry->size.u.HighPart;
1290 entry_info->dwSizeLow = url_entry->size.u.LowPart;
1291 entry_info->dwStructSize = sizeof(*entry_info);
1292 entry_info->dwUseCount = url_entry->use_count;
1293 dos_date_time_to_file_time(url_entry->expire_date, url_entry->expire_time, &entry_info->ExpireTime);
1294 entry_info->LastAccessTime = url_entry->access_time;
1295 entry_info->LastModifiedTime = url_entry->modification_time;
1296 dos_date_time_to_file_time(url_entry->sync_date, url_entry->sync_time, &entry_info->LastSyncTime);
1297 }
1298
1299 if(unicode)
1300 url_len = urlcache_decode_url((const char*)url_entry+url_entry->url_off, NULL, 0);
1301 else
1302 url_len = strlen((LPCSTR)url_entry+url_entry->url_off) + 1;
1303 size += url_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1304
1305 if(*info_size >= size) {
1306 DWORD url_size = url_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1307
1308 entry_info->lpszSourceUrlName = (LPSTR)entry_info+size-url_size;
1309 if(unicode)
1310 urlcache_decode_url((const char*)url_entry+url_entry->url_off, (WCHAR*)entry_info->lpszSourceUrlName, url_len);
1311 else
1312 memcpy(entry_info->lpszSourceUrlName, (LPCSTR)url_entry+url_entry->url_off, url_size);
1313 }
1314
1315 if(size%4 && size<*info_size)
1316 ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1317 size = DWORD_ALIGN(size);
1318
1319 if(url_entry->local_name_off) {
1320 LONG file_name_size;
1321 LPSTR file_name;
1322 file_name = (LPSTR)entry_info+size;
1323 file_name_size = *info_size-size;
1324 if((unicode && urlcache_create_file_pathW(container, header, (LPCSTR)url_entry+url_entry->local_name_off,
1325 url_entry->cache_dir, (LPWSTR)file_name, &file_name_size, FALSE)) ||
1326 (!unicode && urlcache_create_file_pathA(container, header, (LPCSTR)url_entry+url_entry->local_name_off,
1327 url_entry->cache_dir, file_name, &file_name_size))) {
1328 entry_info->lpszLocalFileName = file_name;
1329 }
1330 size += file_name_size;
1331
1332 if(size%4 && size<*info_size)
1333 ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1334 size = DWORD_ALIGN(size);
1335 }
1336
1337 if(url_entry->header_info_off) {
1338 DWORD header_len;
1339
1340 if(unicode)
1341 header_len = MultiByteToWideChar(CP_UTF8, 0, (const char*)url_entry+url_entry->header_info_off,
1342 url_entry->header_info_size, NULL, 0);
1343 else
1344 header_len = url_entry->header_info_size;
1345 size += header_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1346
1347 if(*info_size >= size) {
1348 DWORD header_size = header_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1349 entry_info->lpHeaderInfo = (LPBYTE)entry_info+size-header_size;
1350 if(unicode)
1351 MultiByteToWideChar(CP_UTF8, 0, (const char*)url_entry+url_entry->header_info_off,
1352 url_entry->header_info_size, (LPWSTR)entry_info->lpHeaderInfo, header_len);
1353 else
1354 memcpy(entry_info->lpHeaderInfo, (LPCSTR)url_entry+url_entry->header_info_off, header_len);
1355 }
1356 if(size%4 && size<*info_size)
1357 ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1358 size = DWORD_ALIGN(size);
1359 }
1360
1361 if(url_entry->file_extension_off) {
1362 int ext_len;
1363
1364 if(unicode)
1365 ext_len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)url_entry+url_entry->file_extension_off, -1, NULL, 0);
1366 else
1367 ext_len = strlen((LPCSTR)url_entry+url_entry->file_extension_off) + 1;
1368 size += ext_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1369
1370 if(*info_size >= size) {
1371 DWORD ext_size = ext_len * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
1372 entry_info->lpszFileExtension = (LPSTR)entry_info+size-ext_size;
1373 if(unicode)
1374 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)url_entry+url_entry->file_extension_off, -1, (LPWSTR)entry_info->lpszFileExtension, ext_len);
1375 else
1376 memcpy(entry_info->lpszFileExtension, (LPCSTR)url_entry+url_entry->file_extension_off, ext_len*sizeof(CHAR));
1377 }
1378
1379 if(size%4 && size<*info_size)
1380 ZeroMemory((LPBYTE)entry_info+size, 4-size%4);
1381 size = DWORD_ALIGN(size);
1382 }
1383
1384 if(size > *info_size) {
1385 *info_size = size;
1386 return ERROR_INSUFFICIENT_BUFFER;
1387 }
1388 *info_size = size;
1389 return ERROR_SUCCESS;
1390 }
1391
1392 /***********************************************************************
1393 * urlcache_set_entry_info (Internal)
1394 *
1395 * Helper for SetUrlCacheEntryInfo{A,W}. Sets fields in URL entry
1396 * according to the flags set by field_control.
1397 *
1398 * RETURNS
1399 * ERROR_SUCCESS if the buffer was big enough
1400 * ERROR_INSUFFICIENT_BUFFER if the buffer was too small
1401 *
1402 */
1403 static DWORD urlcache_set_entry_info(entry_url *url_entry, const INTERNET_CACHE_ENTRY_INFOA *entry_info, DWORD field_control)
1404 {
1405 if (field_control & CACHE_ENTRY_ACCTIME_FC)
1406 url_entry->access_time = entry_info->LastAccessTime;
1407 if (field_control & CACHE_ENTRY_ATTRIBUTE_FC)
1408 url_entry->cache_entry_type = entry_info->CacheEntryType;
1409 if (field_control & CACHE_ENTRY_EXEMPT_DELTA_FC)
1410 url_entry->exempt_delta = entry_info->u.dwExemptDelta;
1411 if (field_control & CACHE_ENTRY_EXPTIME_FC)
1412 file_time_to_dos_date_time(&entry_info->ExpireTime, &url_entry->expire_date, &url_entry->expire_time);
1413 if (field_control & CACHE_ENTRY_HEADERINFO_FC)
1414 FIXME("CACHE_ENTRY_HEADERINFO_FC unimplemented\n");
1415 if (field_control & CACHE_ENTRY_HITRATE_FC)
1416 url_entry->hit_rate = entry_info->dwHitRate;
1417 if (field_control & CACHE_ENTRY_MODTIME_FC)
1418 url_entry->modification_time = entry_info->LastModifiedTime;
1419 if (field_control & CACHE_ENTRY_SYNCTIME_FC)
1420 file_time_to_dos_date_time(&entry_info->LastAccessTime, &url_entry->sync_date, &url_entry->sync_time);
1421
1422 return ERROR_SUCCESS;
1423 }
1424
1425 /***********************************************************************
1426 * urlcache_hash_key (Internal)
1427 *
1428 * Returns the hash key for a given string
1429 *
1430 * RETURNS
1431 * hash key for the string
1432 *
1433 */
1434 static DWORD urlcache_hash_key(LPCSTR lpszKey)
1435 {
1436 /* NOTE: this uses the same lookup table as SHLWAPI.UrlHash{A,W}
1437 * but the algorithm and result are not the same!
1438 */
1439 static const unsigned char lookupTable[256] =
1440 {
1441 0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77,
1442 0x8A, 0xAA, 0x7D, 0x76, 0x1B, 0xE9, 0x8C, 0x33,
1443 0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44,
1444 0x1E, 0x07, 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41,
1445 0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94, 0xDF,
1446 0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C,
1447 0x0C, 0xB5, 0x67, 0x46, 0x16, 0x3A, 0x4B, 0x4E,
1448 0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90,
1449 0xB0, 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53,
1450 0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6, 0x29, 0xFE,
1451 0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58,
1452 0x23, 0xCE, 0x5F, 0x74, 0xFC, 0xC0, 0x36, 0xDD,
1453 0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
1454 0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D,
1455 0xA6, 0x50, 0x32, 0x22, 0xAF, 0xC3, 0x64, 0x63,
1456 0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD,
1457 0x79, 0x40, 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A,
1458 0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9, 0xC2,
1459 0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B,
1460 0x4A, 0x3B, 0x89, 0xE4, 0x6C, 0xBF, 0xE8, 0x8B,
1461 0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C,
1462 0xFB, 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70,
1463 0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB, 0x0D, 0x20,
1464 0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B,
1465 0xF9, 0xEC, 0x2D, 0xF4, 0x6F, 0xB6, 0x99, 0x88,
1466 0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
1467 0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72,
1468 0xA2, 0x35, 0xA0, 0xD7, 0xCD, 0xB4, 0x2F, 0x6D,
1469 0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34,
1470 0x3F, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8,
1471 0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB, 0x0A,
1472 0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1
1473 };
1474 BYTE key[4];
1475 DWORD i;
1476
1477 for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
1478 key[i] = lookupTable[(*lpszKey + i) & 0xFF];
1479
1480 for (lpszKey++; *lpszKey; lpszKey++)
1481 {
1482 for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
1483 key[i] = lookupTable[*lpszKey ^ key[i]];
1484 }
1485
1486 return *(DWORD *)key;
1487 }
1488
1489 static inline entry_hash_table* urlcache_get_hash_table(const urlcache_header *pHeader, DWORD dwOffset)
1490 {
1491 if(!dwOffset)
1492 return NULL;
1493 return (entry_hash_table*)((LPBYTE)pHeader + dwOffset);
1494 }
1495
1496 static BOOL urlcache_find_hash_entry(const urlcache_header *pHeader, LPCSTR lpszUrl, struct hash_entry **ppHashEntry)
1497 {
1498 /* structure of hash table:
1499 * 448 entries divided into 64 blocks
1500 * each block therefore contains a chain of 7 key/offset pairs
1501 * how position in table is calculated:
1502 * 1. the url is hashed in helper function
1503 * 2. the key % HASHTABLE_NUM_ENTRIES is the bucket number
1504 * 3. bucket number * HASHTABLE_BLOCKSIZE is offset of the bucket
1505 *
1506 * note:
1507 * there can be multiple hash tables in the file and the offset to
1508 * the next one is stored in the header of the hash table
1509 */
1510 DWORD key = urlcache_hash_key(lpszUrl);
1511 DWORD offset = (key & (HASHTABLE_NUM_ENTRIES-1)) * HASHTABLE_BLOCKSIZE;
1512 entry_hash_table* pHashEntry;
1513 DWORD id = 0;
1514
1515 key >>= HASHTABLE_FLAG_BITS;
1516
1517 for (pHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1518 pHashEntry; pHashEntry = urlcache_get_hash_table(pHeader, pHashEntry->next))
1519 {
1520 int i;
1521 if (pHashEntry->id != id++)
1522 {
1523 ERR("Error: not right hash table number (%d) expected %d\n", pHashEntry->id, id);
1524 continue;
1525 }
1526 /* make sure that it is in fact a hash entry */
1527 if (pHashEntry->header.signature != HASH_SIGNATURE)
1528 {
1529 ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->header.signature);
1530 continue;
1531 }
1532
1533 for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
1534 {
1535 struct hash_entry *pHashElement = &pHashEntry->hash_table[offset + i];
1536 if (key == pHashElement->key>>HASHTABLE_FLAG_BITS)
1537 {
1538 /* FIXME: we should make sure that this is the right element
1539 * before returning and claiming that it is. We can do this
1540 * by doing a simple compare between the URL we were given
1541 * and the URL stored in the entry. However, this assumes
1542 * we know the format of all the entries stored in the
1543 * hash table */
1544 *ppHashEntry = pHashElement;
1545 return TRUE;
1546 }
1547 }
1548 }
1549 return FALSE;
1550 }
1551
1552 /***********************************************************************
1553 * urlcache_hash_entry_set_flags (Internal)
1554 *
1555 * Sets special bits in hash key
1556 *
1557 * RETURNS
1558 * nothing
1559 *
1560 */
1561 static void urlcache_hash_entry_set_flags(struct hash_entry *pHashEntry, DWORD dwFlag)
1562 {
1563 pHashEntry->key = (pHashEntry->key >> HASHTABLE_FLAG_BITS << HASHTABLE_FLAG_BITS) | dwFlag;
1564 }
1565
1566 /***********************************************************************
1567 * urlcache_hash_entry_delete (Internal)
1568 *
1569 * Searches all the hash tables in the index for the given URL and
1570 * then if found deletes the entry.
1571 *
1572 * RETURNS
1573 * TRUE if the entry was found
1574 * FALSE if the entry could not be found
1575 *
1576 */
1577 static BOOL urlcache_hash_entry_delete(struct hash_entry *pHashEntry)
1578 {
1579 pHashEntry->key = HASHTABLE_DEL;
1580 return TRUE;
1581 }
1582
1583 /***********************************************************************
1584 * urlcache_hash_entry_create (Internal)
1585 *
1586 * Searches all the hash tables for a free slot based on the offset
1587 * generated from the hash key. If a free slot is found, the offset and
1588 * key are entered into the hash table.
1589 *
1590 * RETURNS
1591 * ERROR_SUCCESS if the entry was added
1592 * Any other Win32 error code if the entry could not be added
1593 *
1594 */
1595 static DWORD urlcache_hash_entry_create(urlcache_header *pHeader, LPCSTR lpszUrl, DWORD dwOffsetEntry, DWORD dwFieldType)
1596 {
1597 /* see urlcache_find_hash_entry for structure of hash tables */
1598
1599 DWORD key = urlcache_hash_key(lpszUrl);
1600 DWORD offset = (key & (HASHTABLE_NUM_ENTRIES-1)) * HASHTABLE_BLOCKSIZE;
1601 entry_hash_table* pHashEntry, *pHashPrev = NULL;
1602 DWORD id = 0;
1603 DWORD error;
1604
1605 key = ((key >> HASHTABLE_FLAG_BITS) << HASHTABLE_FLAG_BITS) + dwFieldType;
1606
1607 for (pHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1608 pHashEntry; pHashEntry = urlcache_get_hash_table(pHeader, pHashEntry->next))
1609 {
1610 int i;
1611 pHashPrev = pHashEntry;
1612
1613 if (pHashEntry->id != id++)
1614 {
1615 ERR("not right hash table number (%d) expected %d\n", pHashEntry->id, id);
1616 break;
1617 }
1618 /* make sure that it is in fact a hash entry */
1619 if (pHashEntry->header.signature != HASH_SIGNATURE)
1620 {
1621 ERR("not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->header.signature);
1622 break;
1623 }
1624
1625 for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
1626 {
1627 struct hash_entry *pHashElement = &pHashEntry->hash_table[offset + i];
1628 if (pHashElement->key==HASHTABLE_FREE || pHashElement->key==HASHTABLE_DEL) /* if the slot is free */
1629 {
1630 pHashElement->key = key;
1631 pHashElement->offset = dwOffsetEntry;
1632 return ERROR_SUCCESS;
1633 }
1634 }
1635 }
1636 error = urlcache_create_hash_table(pHeader, pHashPrev, &pHashEntry);
1637 if (error != ERROR_SUCCESS)
1638 return error;
1639
1640 pHashEntry->hash_table[offset].key = key;
1641 pHashEntry->hash_table[offset].offset = dwOffsetEntry;
1642 return ERROR_SUCCESS;
1643 }
1644
1645 /***********************************************************************
1646 * urlcache_enum_hash_tables (Internal)
1647 *
1648 * Enumerates the hash tables in a container.
1649 *
1650 * RETURNS
1651 * TRUE if an entry was found
1652 * FALSE if there are no more tables to enumerate.
1653 *
1654 */
1655 static BOOL urlcache_enum_hash_tables(const urlcache_header *pHeader, DWORD *id, entry_hash_table **ppHashEntry)
1656 {
1657 for (*ppHashEntry = urlcache_get_hash_table(pHeader, pHeader->hash_table_off);
1658 *ppHashEntry; *ppHashEntry = urlcache_get_hash_table(pHeader, (*ppHashEntry)->next))
1659 {
1660 TRACE("looking at hash table number %d\n", (*ppHashEntry)->id);
1661 if ((*ppHashEntry)->id != *id)
1662 continue;
1663 /* make sure that it is in fact a hash entry */
1664 if ((*ppHashEntry)->header.signature != HASH_SIGNATURE)
1665 {
1666 ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&(*ppHashEntry)->header.signature);
1667 (*id)++;
1668 continue;
1669 }
1670
1671 TRACE("hash table number %d found\n", *id);
1672 return TRUE;
1673 }
1674 return FALSE;
1675 }
1676
1677 /***********************************************************************
1678 * urlcache_enum_hash_table_entries (Internal)
1679 *
1680 * Enumerates entries in a hash table and returns the next non-free entry.
1681 *
1682 * RETURNS
1683 * TRUE if an entry was found
1684 * FALSE if the hash table is empty or there are no more entries to
1685 * enumerate.
1686 *
1687 */
1688 static BOOL urlcache_enum_hash_table_entries(const urlcache_header *pHeader, const entry_hash_table *pHashEntry,
1689 DWORD * index, const struct hash_entry **ppHashEntry)
1690 {
1691 for (; *index < HASHTABLE_SIZE ; (*index)++)
1692 {
1693 if (pHashEntry->hash_table[*index].key==HASHTABLE_FREE || pHashEntry->hash_table[*index].key==HASHTABLE_DEL)
1694 continue;
1695
1696 *ppHashEntry = &pHashEntry->hash_table[*index];
1697 TRACE("entry found %d\n", *index);
1698 return TRUE;
1699 }
1700 TRACE("no more entries (%d)\n", *index);
1701 return FALSE;
1702 }
1703
1704 /***********************************************************************
1705 * cache_container_delete_dir (Internal)
1706 *
1707 * Erase a directory containing an URL cache.
1708 *
1709 * RETURNS
1710 * TRUE success, FALSE failure/aborted.
1711 *
1712 */
1713 static BOOL cache_container_delete_dir(LPCWSTR lpszPath)
1714 {
1715 DWORD path_len;
1716 WCHAR path[MAX_PATH + 1];
1717 SHFILEOPSTRUCTW shfos;
1718 int ret;
1719
1720 path_len = strlenW(lpszPath);
1721 if (path_len >= MAX_PATH)
1722 return FALSE;
1723 strcpyW(path, lpszPath);
1724 path[path_len + 1] = 0; /* double-NUL-terminate path */
1725
1726 shfos.hwnd = NULL;
1727 shfos.wFunc = FO_DELETE;
1728 shfos.pFrom = path;
1729 shfos.pTo = NULL;
1730 shfos.fFlags = FOF_NOCONFIRMATION;
1731 shfos.fAnyOperationsAborted = FALSE;
1732 ret = SHFileOperationW(&shfos);
1733 if (ret)
1734 ERR("SHFileOperationW on %s returned %i\n", debugstr_w(path), ret);
1735 return !(ret || shfos.fAnyOperationsAborted);
1736 }
1737
1738 /***********************************************************************
1739 * urlcache_hash_entry_is_locked (Internal)
1740 *
1741 * Checks if entry is locked. Unlocks it if possible.
1742 */
1743 static BOOL urlcache_hash_entry_is_locked(struct hash_entry *hash_entry, entry_url *url_entry)
1744 {
1745 FILETIME cur_time;
1746 ULARGE_INTEGER acc_time, time;
1747
1748 if ((hash_entry->key & ((1<<HASHTABLE_FLAG_BITS)-1)) != HASHTABLE_LOCK)
1749 return FALSE;
1750
1751 GetSystemTimeAsFileTime(&cur_time);
1752 time.u.LowPart = cur_time.dwLowDateTime;
1753 time.u.HighPart = cur_time.dwHighDateTime;
1754
1755 acc_time.u.LowPart = url_entry->access_time.dwLowDateTime;
1756 acc_time.u.HighPart = url_entry->access_time.dwHighDateTime;
1757
1758 time.QuadPart -= acc_time.QuadPart;
1759
1760 /* check if entry was locked for at least a day */
1761 if(time.QuadPart > (ULONGLONG)24*60*60*FILETIME_SECOND) {
1762 urlcache_hash_entry_set_flags(hash_entry, HASHTABLE_URL);
1763 url_entry->use_count = 0;
1764 return FALSE;
1765 }
1766
1767 return TRUE;
1768 }
1769
1770 static BOOL urlcache_get_entry_info(const char *url, void *entry_info,
1771 DWORD *size, DWORD flags, BOOL unicode)
1772 {
1773 urlcache_header *header;
1774 struct hash_entry *hash_entry;
1775 const entry_url *url_entry;
1776 cache_container *container;
1777 DWORD error;
1778
1779 TRACE("(%s, %p, %p, %x, %x)\n", debugstr_a(url), entry_info, size, flags, unicode);
1780
1781 if(flags & ~GET_INSTALLED_ENTRY)
1782 FIXME("ignoring unsupported flags: %x\n", flags);
1783
1784 error = cache_containers_find(url, &container);
1785 if(error != ERROR_SUCCESS) {
1786 SetLastError(error);
1787 return FALSE;
1788 }
1789
1790 error = cache_container_open_index(container, MIN_BLOCK_NO);
1791 if(error != ERROR_SUCCESS) {
1792 SetLastError(error);
1793 return FALSE;
1794 }
1795
1796 if(!(header = cache_container_lock_index(container)))
1797 return FALSE;
1798
1799 if(!urlcache_find_hash_entry(header, url, &hash_entry)) {
1800 cache_container_unlock_index(container, header);
1801 WARN("entry %s not found!\n", debugstr_a(url));
1802 SetLastError(ERROR_FILE_NOT_FOUND);
1803 return FALSE;
1804 }
1805
1806 url_entry = (const entry_url*)((LPBYTE)header + hash_entry->offset);
1807 if(url_entry->header.signature != URL_SIGNATURE) {
1808 cache_container_unlock_index(container, header);
1809 FIXME("Trying to retrieve entry of unknown format %s\n",
1810 debugstr_an((LPCSTR)&url_entry->header.signature, sizeof(DWORD)));
1811 SetLastError(ERROR_FILE_NOT_FOUND);
1812 return FALSE;
1813 }
1814
1815 TRACE("Found URL: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->url_off));
1816 TRACE("Header info: %s\n", debugstr_an((LPCSTR)url_entry +
1817 url_entry->header_info_off, url_entry->header_info_size));
1818
1819 if((flags & GET_INSTALLED_ENTRY) && !(url_entry->cache_entry_type & INSTALLED_CACHE_ENTRY)) {
1820 cache_container_unlock_index(container, header);
1821 SetLastError(ERROR_FILE_NOT_FOUND);
1822 return FALSE;
1823 }
1824
1825 if(size) {
1826 if(!entry_info)
1827 *size = 0;
1828
1829 error = urlcache_copy_entry(container, header, entry_info, size, url_entry, unicode);
1830 if(error != ERROR_SUCCESS) {
1831 cache_container_unlock_index(container, header);
1832 SetLastError(error);
1833 return FALSE;
1834 }
1835 if(url_entry->local_name_off)
1836 TRACE("Local File Name: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->local_name_off));
1837 }
1838
1839 cache_container_unlock_index(container, header);
1840 return TRUE;
1841 }
1842
1843 /***********************************************************************
1844 * GetUrlCacheEntryInfoExA (WININET.@)
1845 *
1846 */
1847 BOOL WINAPI GetUrlCacheEntryInfoExA(LPCSTR lpszUrl,
1848 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1849 LPDWORD lpdwCacheEntryInfoBufSize, LPSTR lpszReserved,
1850 LPDWORD lpdwReserved, LPVOID lpReserved, DWORD dwFlags)
1851 {
1852 if(lpszReserved!=NULL || lpdwReserved!=NULL || lpReserved!=NULL) {
1853 ERR("Reserved value was not 0\n");
1854 SetLastError(ERROR_INVALID_PARAMETER);
1855 return FALSE;
1856 }
1857
1858 return urlcache_get_entry_info(lpszUrl, lpCacheEntryInfo,
1859 lpdwCacheEntryInfoBufSize, dwFlags, FALSE);
1860 }
1861
1862 /***********************************************************************
1863 * GetUrlCacheEntryInfoA (WININET.@)
1864 *
1865 */
1866 BOOL WINAPI GetUrlCacheEntryInfoA(LPCSTR lpszUrlName,
1867 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1868 LPDWORD lpdwCacheEntryInfoBufferSize)
1869 {
1870 return GetUrlCacheEntryInfoExA(lpszUrlName, lpCacheEntryInfo,
1871 lpdwCacheEntryInfoBufferSize, NULL, NULL, NULL, 0);
1872 }
1873
1874 static int urlcache_encode_url(const WCHAR *url, char *encoded_url, int encoded_len)
1875 {
1876 URL_COMPONENTSW uc;
1877 DWORD len, part_len;
1878 WCHAR *punycode;
1879
1880 TRACE("%s\n", debugstr_w(url));
1881
1882 memset(&uc, 0, sizeof(uc));
1883 uc.dwStructSize = sizeof(uc);
1884 uc.dwHostNameLength = 1;
1885 if(!InternetCrackUrlW(url, 0, 0, &uc))
1886 uc.nScheme = INTERNET_SCHEME_UNKNOWN;
1887
1888 if(uc.nScheme!=INTERNET_SCHEME_HTTP && uc.nScheme!=INTERNET_SCHEME_HTTPS)
1889 return WideCharToMultiByte(CP_UTF8, 0, url, -1, encoded_url, encoded_len, NULL, NULL);
1890
1891 len = WideCharToMultiByte(CP_UTF8, 0, url, uc.lpszHostName-url,
1892 encoded_url, encoded_len, NULL, NULL);
1893 if(!len)
1894 return 0;
1895 if(encoded_url)
1896 encoded_len -= len;
1897
1898 part_len = IdnToAscii(0, uc.lpszHostName, uc.dwHostNameLength, NULL, 0);
1899 if(!part_len) {
1900 SetLastError(ERROR_INTERNET_INVALID_URL);
1901 return 0;
1902 }
1903
1904 punycode = heap_alloc(part_len*sizeof(WCHAR));
1905 if(!punycode)
1906 return 0;
1907
1908 part_len = IdnToAscii(0, uc.lpszHostName, uc.dwHostNameLength, punycode, part_len);
1909 if(!part_len) {
1910 heap_free(punycode);
1911 return 0;
1912 }
1913
1914 part_len = WideCharToMultiByte(CP_UTF8, 0, punycode, part_len,
1915 encoded_url ? encoded_url+len : NULL, encoded_len, NULL, NULL);
1916 heap_free(punycode);
1917 if(!part_len)
1918 return 0;
1919 if(encoded_url)
1920 encoded_len -= part_len;
1921 len += part_len;
1922
1923 part_len = WideCharToMultiByte(CP_UTF8, 0, uc.lpszHostName+uc.dwHostNameLength,
1924 -1, encoded_url ? encoded_url+len : NULL, encoded_len, NULL, NULL);
1925 if(!part_len)
1926 return 0;
1927 len += part_len;
1928
1929 TRACE("got (%d)%s\n", len, debugstr_a(encoded_url));
1930 return len;
1931 }
1932
1933 static BOOL urlcache_encode_url_alloc(const WCHAR *url, char **encoded_url)
1934 {
1935 DWORD encoded_len;
1936 char *ret;
1937
1938 encoded_len = urlcache_encode_url(url, NULL, 0);
1939 if(!encoded_len)
1940 return FALSE;
1941
1942 ret = heap_alloc(encoded_len*sizeof(WCHAR));
1943 if(!ret)
1944 return FALSE;
1945
1946 encoded_len = urlcache_encode_url(url, ret, encoded_len);
1947 if(!encoded_len) {
1948 heap_free(ret);
1949 return FALSE;
1950 }
1951
1952 *encoded_url = ret;
1953 return TRUE;
1954 }
1955
1956 /***********************************************************************
1957 * GetUrlCacheEntryInfoExW (WININET.@)
1958 *
1959 */
1960 BOOL WINAPI GetUrlCacheEntryInfoExW(LPCWSTR lpszUrl,
1961 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
1962 LPDWORD lpdwCacheEntryInfoBufSize, LPWSTR lpszReserved,
1963 LPDWORD lpdwReserved, LPVOID lpReserved, DWORD dwFlags)
1964 {
1965 char *url;
1966 BOOL ret;
1967
1968 if(lpszReserved!=NULL || lpdwReserved!=NULL || lpReserved!=NULL) {
1969 ERR("Reserved value was not 0\n");
1970 SetLastError(ERROR_INVALID_PARAMETER);
1971 return FALSE;
1972 }
1973
1974 /* Ignore GET_INSTALLED_ENTRY flag in unicode version of function */
1975 dwFlags &= ~GET_INSTALLED_ENTRY;
1976
1977 if(!urlcache_encode_url_alloc(lpszUrl, &url))
1978 return FALSE;
1979
1980 ret = urlcache_get_entry_info(url, lpCacheEntryInfo,
1981 lpdwCacheEntryInfoBufSize, dwFlags, TRUE);
1982 heap_free(url);
1983 return ret;
1984 }
1985
1986 /***********************************************************************
1987 * GetUrlCacheEntryInfoW (WININET.@)
1988 *
1989 */
1990 BOOL WINAPI GetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
1991 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
1992 LPDWORD lpdwCacheEntryInfoBufferSize)
1993 {
1994 return GetUrlCacheEntryInfoExW(lpszUrl, lpCacheEntryInfo,
1995 lpdwCacheEntryInfoBufferSize, NULL, NULL, NULL, 0);
1996 }
1997
1998 /***********************************************************************
1999 * SetUrlCacheEntryInfoA (WININET.@)
2000 */
2001 BOOL WINAPI SetUrlCacheEntryInfoA(LPCSTR lpszUrlName,
2002 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
2003 DWORD dwFieldControl)
2004 {
2005 urlcache_header *pHeader;
2006 struct hash_entry *pHashEntry;
2007 entry_header *pEntry;
2008 cache_container *pContainer;
2009 DWORD error;
2010
2011 TRACE("(%s, %p, 0x%08x)\n", debugstr_a(lpszUrlName), lpCacheEntryInfo, dwFieldControl);
2012
2013 error = cache_containers_find(lpszUrlName, &pContainer);
2014 if (error != ERROR_SUCCESS)
2015 {
2016 SetLastError(error);
2017 return FALSE;
2018 }
2019
2020 error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
2021 if (error != ERROR_SUCCESS)
2022 {
2023 SetLastError(error);
2024 return FALSE;
2025 }
2026
2027 if (!(pHeader = cache_container_lock_index(pContainer)))
2028 return FALSE;
2029
2030 if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
2031 {
2032 cache_container_unlock_index(pContainer, pHeader);
2033 WARN("entry %s not found!\n", debugstr_a(lpszUrlName));
2034 SetLastError(ERROR_FILE_NOT_FOUND);
2035 return FALSE;
2036 }
2037
2038 pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2039 if (pEntry->signature != URL_SIGNATURE)
2040 {
2041 cache_container_unlock_index(pContainer, pHeader);
2042 FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->signature, sizeof(DWORD)));
2043 SetLastError(ERROR_FILE_NOT_FOUND);
2044 return FALSE;
2045 }
2046
2047 urlcache_set_entry_info((entry_url*)pEntry, lpCacheEntryInfo, dwFieldControl);
2048
2049 cache_container_unlock_index(pContainer, pHeader);
2050
2051 return TRUE;
2052 }
2053
2054 /***********************************************************************
2055 * SetUrlCacheEntryInfoW (WININET.@)
2056 */
2057 BOOL WINAPI SetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
2058 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2059 DWORD dwFieldControl)
2060 {
2061 char *url;
2062 BOOL ret;
2063
2064 if(!urlcache_encode_url_alloc(lpszUrl, &url))
2065 return FALSE;
2066
2067 ret = SetUrlCacheEntryInfoA(url, (INTERNET_CACHE_ENTRY_INFOA*)lpCacheEntryInfo, dwFieldControl);
2068 heap_free(url);
2069 return ret;
2070 }
2071
2072 static BOOL urlcache_entry_get_file(const char *url, void *entry_info, DWORD *size, BOOL unicode)
2073 {
2074 urlcache_header *header;
2075 struct hash_entry *hash_entry;
2076 entry_url *url_entry;
2077 cache_container *container;
2078 DWORD error;
2079
2080 TRACE("(%s, %p, %p, %x)\n", debugstr_a(url), entry_info, size, unicode);
2081
2082 if(!url || !size || (!entry_info && *size)) {
2083 SetLastError(ERROR_INVALID_PARAMETER);
2084 return FALSE;
2085 }
2086
2087 error = cache_containers_find(url, &container);
2088 if(error != ERROR_SUCCESS) {
2089 SetLastError(error);
2090 return FALSE;
2091 }
2092
2093 error = cache_container_open_index(container, MIN_BLOCK_NO);
2094 if (error != ERROR_SUCCESS) {
2095 SetLastError(error);
2096 return FALSE;
2097 }
2098
2099 if (!(header = cache_container_lock_index(container)))
2100 return FALSE;
2101
2102 if (!urlcache_find_hash_entry(header, url, &hash_entry)) {
2103 cache_container_unlock_index(container, header);
2104 TRACE("entry %s not found!\n", url);
2105 SetLastError(ERROR_FILE_NOT_FOUND);
2106 return FALSE;
2107 }
2108
2109 url_entry = (entry_url*)((LPBYTE)header + hash_entry->offset);
2110 if(url_entry->header.signature != URL_SIGNATURE) {
2111 cache_container_unlock_index(container, header);
2112 FIXME("Trying to retrieve entry of unknown format %s\n",
2113 debugstr_an((LPSTR)&url_entry->header.signature, sizeof(DWORD)));
2114 SetLastError(ERROR_FILE_NOT_FOUND);
2115 return FALSE;
2116 }
2117
2118 if(!url_entry->local_name_off) {
2119 cache_container_unlock_index(container, header);
2120 SetLastError(ERROR_INVALID_DATA);
2121 return FALSE;
2122 }
2123
2124 TRACE("Found URL: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->url_off));
2125 TRACE("Header info: %s\n", debugstr_an((LPCSTR)url_entry + url_entry->header_info_off,
2126 url_entry->header_info_size));
2127
2128 error = urlcache_copy_entry(container, header, entry_info,
2129 size, url_entry, unicode);
2130 if(error != ERROR_SUCCESS) {
2131 cache_container_unlock_index(container, header);
2132 SetLastError(error);
2133 return FALSE;
2134 }
2135 TRACE("Local File Name: %s\n", debugstr_a((LPCSTR)url_entry + url_entry->local_name_off));
2136
2137 url_entry->hit_rate++;
2138 url_entry->use_count++;
2139 urlcache_hash_entry_set_flags(hash_entry, HASHTABLE_LOCK);
2140 GetSystemTimeAsFileTime(&url_entry->access_time);
2141
2142 cache_container_unlock_index(container, header);
2143
2144 return TRUE;
2145 }
2146
2147 /***********************************************************************
2148 * RetrieveUrlCacheEntryFileA (WININET.@)
2149 *
2150 */
2151 BOOL WINAPI RetrieveUrlCacheEntryFileA(LPCSTR lpszUrlName,
2152 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
2153 LPDWORD lpdwCacheEntryInfoBufferSize, DWORD dwReserved)
2154 {
2155 return urlcache_entry_get_file(lpszUrlName, lpCacheEntryInfo,
2156 lpdwCacheEntryInfoBufferSize, FALSE);
2157 }
2158
2159 /***********************************************************************
2160 * RetrieveUrlCacheEntryFileW (WININET.@)
2161 *
2162 */
2163 BOOL WINAPI RetrieveUrlCacheEntryFileW(LPCWSTR lpszUrlName,
2164 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
2165 LPDWORD lpdwCacheEntryInfoBufferSize, DWORD dwReserved)
2166 {
2167 char *url;
2168 BOOL ret;
2169
2170 if(!urlcache_encode_url_alloc(lpszUrlName, &url))
2171 return FALSE;
2172
2173 ret = urlcache_entry_get_file(url, lpCacheEntryInfo,
2174 lpdwCacheEntryInfoBufferSize, TRUE);
2175 heap_free(url);
2176 return ret;
2177 }
2178
2179 static BOOL urlcache_entry_delete(const cache_container *pContainer,
2180 urlcache_header *pHeader, struct hash_entry *pHashEntry)
2181 {
2182 entry_header *pEntry;
2183 entry_url * pUrlEntry;
2184
2185 pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2186 if (pEntry->signature != URL_SIGNATURE)
2187 {
2188 FIXME("Trying to delete entry of unknown format %s\n",
2189 debugstr_an((LPCSTR)&pEntry->signature, sizeof(DWORD)));
2190 SetLastError(ERROR_FILE_NOT_FOUND);
2191 return FALSE;
2192 }
2193
2194 pUrlEntry = (entry_url *)pEntry;
2195 if(urlcache_hash_entry_is_locked(pHashEntry, pUrlEntry))
2196 {
2197 TRACE("Trying to delete locked entry\n");
2198 pUrlEntry->cache_entry_type |= PENDING_DELETE_CACHE_ENTRY;
2199 SetLastError(ERROR_SHARING_VIOLATION);
2200 return FALSE;
2201 }
2202
2203 if(!urlcache_delete_file(pContainer, pHeader, pUrlEntry))
2204 {
2205 urlcache_entry_free(pHeader, pEntry);
2206 }
2207 else
2208 {
2209 /* Add entry to leaked files list */
2210 pUrlEntry->header.signature = LEAK_SIGNATURE;
2211 pUrlEntry->exempt_delta = pHeader->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET];
2212 pHeader->options[CACHE_HEADER_DATA_ROOT_LEAK_OFFSET] = pHashEntry->offset;
2213 }
2214
2215 urlcache_hash_entry_delete(pHashEntry);
2216 return TRUE;
2217 }
2218
2219 static HANDLE free_cache_running;
2220 static HANDLE dll_unload_event;
2221 static DWORD WINAPI handle_full_cache_worker(void *param)
2222 {
2223 FreeUrlCacheSpaceW(NULL, 20, 0);
2224 ReleaseSemaphore(free_cache_running, 1, NULL);
2225 return 0;
2226 }
2227
2228 static void handle_full_cache(void)
2229 {
2230 if(WaitForSingleObject(free_cache_running, 0) == WAIT_OBJECT_0) {
2231 if(!QueueUserWorkItem(handle_full_cache_worker, NULL, 0))
2232 ReleaseSemaphore(free_cache_running, 1, NULL);
2233 }
2234 }
2235
2236 /* Enumerates entries in cache, allows cache unlocking between calls. */
2237 static BOOL urlcache_next_entry(urlcache_header *header, DWORD *hash_table_off, DWORD *hash_table_entry,
2238 struct hash_entry **hash_entry, entry_header **entry)
2239 {
2240 entry_hash_table *hashtable_entry;
2241
2242 *hash_entry = NULL;
2243 *entry = NULL;
2244
2245 if(!*hash_table_off) {
2246 *hash_table_off = header->hash_table_off;
2247 *hash_table_entry = 0;
2248
2249 hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2250 }else {
2251 if(*hash_table_off >= header->size) {
2252 *hash_table_off = 0;
2253 return FALSE;
2254 }
2255
2256 hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2257 }
2258
2259 if(hashtable_entry->header.signature != HASH_SIGNATURE) {
2260 *hash_table_off = 0;
2261 return FALSE;
2262 }
2263
2264 while(1) {
2265 if(*hash_table_entry >= HASHTABLE_SIZE) {
2266 *hash_table_off = hashtable_entry->next;
2267 if(!*hash_table_off) {
2268 *hash_table_off = 0;
2269 return FALSE;
2270 }
2271
2272 hashtable_entry = urlcache_get_hash_table(header, *hash_table_off);
2273 *hash_table_entry = 0;
2274 }
2275
2276 if(hashtable_entry->hash_table[*hash_table_entry].key != HASHTABLE_DEL &&
2277 hashtable_entry->hash_table[*hash_table_entry].key != HASHTABLE_FREE) {
2278 *hash_entry = &hashtable_entry->hash_table[*hash_table_entry];
2279 *entry = (entry_header*)((LPBYTE)header + hashtable_entry->hash_table[*hash_table_entry].offset);
2280 (*hash_table_entry)++;
2281 return TRUE;
2282 }
2283
2284 (*hash_table_entry)++;
2285 }
2286
2287 *hash_table_off = 0;
2288 return FALSE;
2289 }
2290
2291 /* Rates an urlcache entry to determine if it can be deleted.
2292 *
2293 * Score 0 means that entry can safely be removed, the bigger rating
2294 * the smaller chance of entry being removed.
2295 * DWORD_MAX means that entry can't be deleted at all.
2296 *
2297 * Rating system is currently not fully compatible with native implementation.
2298 */
2299 static DWORD urlcache_rate_entry(entry_url *url_entry, FILETIME *cur_time)
2300 {
2301 ULARGE_INTEGER time, access_time;
2302 DWORD rating;
2303
2304 access_time.u.LowPart = url_entry->access_time.dwLowDateTime;
2305 access_time.u.HighPart = url_entry->access_time.dwHighDateTime;
2306
2307 time.u.LowPart = cur_time->dwLowDateTime;
2308 time.u.HighPart = cur_time->dwHighDateTime;
2309
2310 /* Don't touch entries that were added less than 10 minutes ago */
2311 if(time.QuadPart < access_time.QuadPart + (ULONGLONG)10*60*FILETIME_SECOND)
2312 return -1;
2313
2314 if(url_entry->cache_entry_type & STICKY_CACHE_ENTRY)
2315 if(time.QuadPart < access_time.QuadPart + (ULONGLONG)url_entry->exempt_delta*FILETIME_SECOND)
2316 return -1;
2317
2318 time.QuadPart = (time.QuadPart-access_time.QuadPart)/FILETIME_SECOND;
2319 rating = 400*60*60*24/(60*60*24+time.QuadPart);
2320
2321 if(url_entry->hit_rate > 100)
2322 rating += 100;
2323 else
2324 rating += url_entry->hit_rate;
2325
2326 return rating;
2327 }
2328
2329 static int dword_cmp(const void *p1, const void *p2)
2330 {
2331 return *(const DWORD*)p1 - *(const DWORD*)p2;
2332 }
2333
2334 /***********************************************************************
2335 * FreeUrlCacheSpaceW (WININET.@)
2336 *
2337 * Frees up some cache.
2338 *
2339 * PARAMETERS
2340 * cache_path [I] Which volume to free up from, or NULL if you don't care.
2341 * size [I] Percentage of the cache that should be free.
2342 * filter [I] Which entries can't be deleted (CacheEntryType)
2343 *
2344 * RETURNS
2345 * TRUE success. FALSE failure.
2346 *
2347 * IMPLEMENTATION
2348 * This implementation just retrieves the path of the cache directory, and
2349 * deletes its contents from the filesystem. The correct approach would
2350 * probably be to implement and use {FindFirst,FindNext,Delete}UrlCacheGroup().
2351 */
2352 BOOL WINAPI FreeUrlCacheSpaceW(LPCWSTR cache_path, DWORD size, DWORD filter)
2353 {
2354 cache_container *container;
2355 DWORD path_len, err;
2356
2357 TRACE("(%s, %x, %x)\n", debugstr_w(cache_path), size, filter);
2358
2359 if(size<1 || size>100) {
2360 SetLastError(ERROR_INVALID_PARAMETER);
2361 return FALSE;
2362 }
2363
2364 if(cache_path) {
2365 path_len = strlenW(cache_path);
2366 if(cache_path[path_len-1] == '\\')
2367 path_len--;
2368 }else {
2369 path_len = 0;
2370 }
2371
2372 if(size==100 && !filter) {
2373 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
2374 {
2375 /* When cache_path==NULL only clean Temporary Internet Files */
2376 if((!path_len && container->cache_prefix[0]==0) ||
2377 (path_len && !strncmpiW(container->path, cache_path, path_len) &&
2378 (container->path[path_len]=='\0' || container->path[path_len]=='\\')))
2379 {
2380 BOOL ret_del;
2381
2382 WaitForSingleObject(container->mutex, INFINITE);
2383
2384 /* unlock, delete, recreate and lock cache */
2385 cache_container_close_index(container);
2386 ret_del = cache_container_delete_dir(container->path);
2387 err = cache_container_open_index(container, MIN_BLOCK_NO);
2388
2389 ReleaseMutex(container->mutex);
2390 if(!ret_del || (err != ERROR_SUCCESS))
2391 return FALSE;
2392 }
2393 }
2394
2395 return TRUE;
2396 }
2397
2398 LIST_FOR_EACH_ENTRY(container, &UrlContainers, cache_container, entry)
2399 {
2400 urlcache_header *header;
2401 struct hash_entry *hash_entry;
2402 entry_header *entry;
2403 entry_url *url_entry;
2404 ULONGLONG desired_size, cur_size;
2405 DWORD delete_factor, hash_table_off, hash_table_entry;
2406 DWORD rate[100], rate_no;
2407 FILETIME cur_time;
2408
2409 if((path_len || container->cache_prefix[0]!=0) &&
2410 (!path_len || strncmpiW(container->path, cache_path, path_len) ||
2411 (container->path[path_len]!='\0' && container->path[path_len]!='\\')))
2412 continue;
2413
2414 err = cache_container_open_index(container, MIN_BLOCK_NO);
2415 if(err != ERROR_SUCCESS)
2416 continue;
2417
2418 header = cache_container_lock_index(container);
2419 if(!header)
2420 continue;
2421
2422 urlcache_clean_leaked_entries(container, header);
2423
2424 desired_size = header->cache_limit.QuadPart*(100-size)/100;
2425 cur_size = header->cache_usage.QuadPart+header->exempt_usage.QuadPart;
2426 if(cur_size <= desired_size)
2427 delete_factor = 0;
2428 else
2429 delete_factor = (cur_size-desired_size)*100/cur_size;
2430
2431 if(!delete_factor) {
2432 cache_container_unlock_index(container, header);
2433 continue;
2434 }
2435
2436 hash_table_off = 0;
2437 hash_table_entry = 0;
2438 rate_no = 0;
2439 GetSystemTimeAsFileTime(&cur_time);
2440 while(rate_no<sizeof(rate)/sizeof(*rate) &&
2441 urlcache_next_entry(header, &hash_table_off, &hash_table_entry, &hash_entry, &entry)) {
2442 if(entry->signature != URL_SIGNATURE) {
2443 WARN("only url entries are currently supported\n");
2444 continue;
2445 }
2446
2447 url_entry = (entry_url*)entry;
2448 if(url_entry->cache_entry_type & filter)
2449 continue;
2450
2451 rate[rate_no] = urlcache_rate_entry(url_entry, &cur_time);
2452 if(rate[rate_no] != -1)
2453 rate_no++;
2454 }
2455
2456 if(!rate_no) {
2457 TRACE("nothing to delete\n");
2458 cache_container_unlock_index(container, header);
2459 continue;
2460 }
2461
2462 qsort(rate, rate_no, sizeof(DWORD), dword_cmp);
2463
2464 delete_factor = delete_factor*rate_no/100;
2465 delete_factor = rate[delete_factor];
2466 TRACE("deleting files with rating %d or less\n", delete_factor);
2467
2468 hash_table_off = 0;
2469 while(urlcache_next_entry(header, &hash_table_off, &hash_table_entry, &hash_entry, &entry)) {
2470 if(entry->signature != URL_SIGNATURE)
2471 continue;
2472
2473 url_entry = (entry_url*)entry;
2474 if(url_entry->cache_entry_type & filter)
2475 continue;
2476
2477 if(urlcache_rate_entry(url_entry, &cur_time) <= delete_factor) {
2478 TRACE("deleting file: %s\n", debugstr_a((char*)url_entry+url_entry->local_name_off));
2479 urlcache_entry_delete(container, header, hash_entry);
2480
2481 if(header->cache_usage.QuadPart+header->exempt_usage.QuadPart <= desired_size)
2482 break;
2483
2484 /* Allow other threads to use cache while cleaning */
2485 cache_container_unlock_index(container, header);
2486 if(WaitForSingleObject(dll_unload_event, 0) == WAIT_OBJECT_0) {
2487 TRACE("got dll_unload_event - finishing\n");
2488 return TRUE;
2489 }
2490 Sleep(0);
2491 header = cache_container_lock_index(container);
2492 }
2493 }
2494
2495 TRACE("cache size after cleaning 0x%s/0x%s\n",
2496 wine_dbgstr_longlong(header->cache_usage.QuadPart+header->exempt_usage.QuadPart),
2497 wine_dbgstr_longlong(header->cache_limit.QuadPart));
2498 cache_container_unlock_index(container, header);
2499 }
2500
2501 return TRUE;
2502 }
2503
2504 /***********************************************************************
2505 * FreeUrlCacheSpaceA (WININET.@)
2506 *
2507 * See FreeUrlCacheSpaceW.
2508 */
2509 BOOL WINAPI FreeUrlCacheSpaceA(LPCSTR lpszCachePath, DWORD dwSize, DWORD dwFilter)
2510 {
2511 BOOL ret = FALSE;
2512 LPWSTR path = heap_strdupAtoW(lpszCachePath);
2513 if (lpszCachePath == NULL || path != NULL)
2514 ret = FreeUrlCacheSpaceW(path, dwSize, dwFilter);
2515 heap_free(path);
2516 return ret;
2517 }
2518
2519 /***********************************************************************
2520 * UnlockUrlCacheEntryFileA (WININET.@)
2521 *
2522 */
2523 BOOL WINAPI UnlockUrlCacheEntryFileA(LPCSTR lpszUrlName, DWORD dwReserved)
2524 {
2525 urlcache_header *pHeader;
2526 struct hash_entry *pHashEntry;
2527 entry_header *pEntry;
2528 entry_url * pUrlEntry;
2529 cache_container *pContainer;
2530 DWORD error;
2531
2532 TRACE("(%s, 0x%08x)\n", debugstr_a(lpszUrlName), dwReserved);
2533
2534 if (dwReserved)
2535 {
2536 ERR("dwReserved != 0\n");
2537 SetLastError(ERROR_INVALID_PARAMETER);
2538 return FALSE;
2539 }
2540
2541 error = cache_containers_find(lpszUrlName, &pContainer);
2542 if (error != ERROR_SUCCESS)
2543 {
2544 SetLastError(error);
2545 return FALSE;
2546 }
2547
2548 error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
2549 if (error != ERROR_SUCCESS)
2550 {
2551 SetLastError(error);
2552 return FALSE;
2553 }
2554
2555 if (!(pHeader = cache_container_lock_index(pContainer)))
2556 return FALSE;
2557
2558 if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
2559 {
2560 cache_container_unlock_index(pContainer, pHeader);
2561 TRACE("entry %s not found!\n", lpszUrlName);
2562 SetLastError(ERROR_FILE_NOT_FOUND);
2563 return FALSE;
2564 }
2565
2566 pEntry = (entry_header*)((LPBYTE)pHeader + pHashEntry->offset);
2567 if (pEntry->signature != URL_SIGNATURE)
2568 {
2569 cache_container_unlock_index(pContainer, pHeader);
2570 FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->signature, sizeof(DWORD)));
2571 SetLastError(ERROR_FILE_NOT_FOUND);
2572 return FALSE;
2573 }
2574
2575 pUrlEntry = (entry_url *)pEntry;
2576
2577 if (pUrlEntry->use_count == 0)
2578 {
2579 cache_container_unlock_index(pContainer, pHeader);
2580 return FALSE;
2581 }
2582 pUrlEntry->use_count--;
2583 if (!pUrlEntry->use_count)
2584 {
2585 urlcache_hash_entry_set_flags(pHashEntry, HASHTABLE_URL);
2586 if (pUrlEntry->cache_entry_type & PENDING_DELETE_CACHE_ENTRY)
2587 urlcache_entry_delete(pContainer, pHeader, pHashEntry);
2588 }
2589
2590 cache_container_unlock_index(pContainer, pHeader);
2591
2592 return TRUE;
2593 }
2594
2595 /***********************************************************************
2596 * UnlockUrlCacheEntryFileW (WININET.@)
2597 *
2598 */
2599 BOOL WINAPI UnlockUrlCacheEntryFileW(LPCWSTR lpszUrlName, DWORD dwReserved)
2600 {
2601 char *url;
2602 BOOL ret;
2603
2604 if(!urlcache_encode_url_alloc(lpszUrlName, &url))
2605 return FALSE;
2606
2607 ret = UnlockUrlCacheEntryFileA(url, dwReserved);
2608 heap_free(url);
2609 return ret;
2610 }
2611
2612 static BOOL urlcache_entry_create(const char *url, const char *ext, WCHAR *full_path)
2613 {
2614 cache_container *container;
2615 urlcache_header *header;
2616 char file_name[MAX_PATH];
2617 WCHAR extW[MAX_PATH];
2618 BYTE cache_dir;
2619 LONG full_path_len, ext_len = 0;
2620 BOOL generate_name = FALSE;
2621 DWORD error;
2622 HANDLE file;
2623 FILETIME ft;
2624 URL_COMPONENTSA uc;
2625 int i;
2626
2627 TRACE("(%s, %s, %p)\n", debugstr_a(url), debugstr_a(ext), full_path);
2628
2629 memset(&uc, 0, sizeof(uc));
2630 uc.dwStructSize = sizeof(uc);
2631 uc.dwUrlPathLength = 1;
2632 uc.dwExtraInfoLength = 1;
2633 if(!InternetCrackUrlA(url, 0, 0, &uc))
2634 uc.dwUrlPathLength = 0;
2635
2636 if(!uc.dwUrlPathLength) {
2637 file_name[0] = 0;
2638 }else {
2639 char *p, *e;
2640
2641 p = e = uc.lpszUrlPath+uc.dwUrlPathLength;
2642 while(p>uc.lpszUrlPath && *(p-1)!='/' && *(p-1)!='\\' && *(p-1)!='.')
2643 p--;
2644 if(p>uc.lpszUrlPath && *(p-1)=='.') {
2645 e = p-1;
2646 while(p>uc.lpszUrlPath && *(p-1)!='/' && *(p-1)!='\\')
2647 p--;
2648 }
2649
2650 if(e-p >= MAX_PATH)
2651 e = p+MAX_PATH-1;
2652 memcpy(file_name, p, e-p);
2653 file_name[e-p] = 0;
2654
2655 for(p=file_name; *p; p++) {
2656 switch(*p) {
2657 case '<': case '>':
2658 case ':': case '"':
2659 case '|': case '?':
2660 case '*':
2661 *p = '_'; break;
2662 default: break;
2663 }
2664 }
2665 }
2666
2667 if(!file_name[0])
2668 generate_name = TRUE;
2669
2670 error = cache_containers_find(url, &container);
2671 if(error != ERROR_SUCCESS) {
2672 SetLastError(error);
2673 return FALSE;
2674 }
2675
2676 error = cache_container_open_index(container, MIN_BLOCK_NO);
2677 if(error != ERROR_SUCCESS) {
2678 SetLastError(error);
2679 return FALSE;
2680 }
2681
2682 if(!(header = cache_container_lock_index(container)))
2683 return FALSE;
2684
2685 if(header->dirs_no)
2686 cache_dir = (BYTE)(rand() % header->dirs_no);
2687 else
2688 cache_dir = CACHE_CONTAINER_NO_SUBDIR;
2689
2690 full_path_len = MAX_PATH * sizeof(WCHAR);
2691 if(!urlcache_create_file_pathW(container, header, file_name, cache_dir, full_path, &full_path_len, TRUE)) {
2692 WARN("Failed to get full path for filename %s, needed %u bytes.\n",
2693 debugstr_a(file_name), full_path_len);
2694 cache_container_unlock_index(container, header);
2695 return FALSE;
2696 }
2697 full_path_len = full_path_len/sizeof(WCHAR) - 1;
2698
2699 cache_container_unlock_index(container, header);
2700
2701 if(ext) {
2702 WCHAR *p;
2703
2704 extW[0] = '.';
2705 ext_len = MultiByteToWideChar(CP_ACP, 0, ext, -1, extW+1, MAX_PATH-1);
2706
2707 for(p=extW; *p; p++) {
2708 switch(*p) {
2709 case '<': case '>':
2710 case ':': case '"':
2711 case '|': case '?':
2712 case '*':
2713 *p = '_'; break;
2714 default: break;
2715 }
2716 }
2717 if(p[-1]==' ' || p[-1]=='.')
2718 p[-1] = '_';
2719 }else {
2720 extW[0] = '\0';
2721 }
2722
2723 if(!generate_name && full_path_len+5+ext_len>=MAX_PATH) { /* strlen("[255]") = 5 */
2724 full_path_len = MAX_PATH-5-ext_len-1;
2725 }
2726
2727 for(i=0; i<255 && !generate_name; i++) {
2728 static const WCHAR format[] = {'[','%','u',']','%','s',0};
2729
2730 wsprintfW(full_path+full_path_len, format, i, extW);
2731
2732 TRACE("Trying: %s\n", debugstr_w(full_path));
2733 file = CreateFileW(full_path, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
2734 if(file != INVALID_HANDLE_VALUE) {
2735 CloseHandle(file);
2736 return TRUE;
2737 }
2738 }
2739
2740 if(full_path_len+8+ext_len >= MAX_PATH)
2741 full_path_len = MAX_PATH-8-ext_len-1;
2742
2743 /* Try to generate random name */
2744 GetSystemTimeAsFileTime(&ft);
2745 strcpyW(full_path+full_path_len+8, extW);
2746
2747 for(i=0; i<255; i++) {
2748 int j;
2749 ULONGLONG n = ft.dwHighDateTime;
2750 n <<= 32;
2751 n += ft.dwLowDateTime;
2752 n ^= (ULONGLONG)i<<48;
2753
2754 for(j=0; j<8; j++) {
2755 int r = (n % 36);
2756 n /= 37;
2757 full_path[full_path_len+j] = (r < 10 ? '0' + r : 'A' + r - 10);
2758 }
2759
2760 TRACE("Trying: %s\n", debugstr_w(full_path));
2761 file = CreateFileW(full_path, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
2762 if(file != INVALID_HANDLE_VALUE) {
2763 CloseHandle(file);
2764 return TRUE;
2765 }
2766 }
2767
2768 WARN("Could not find a unique filename\n");
2769 return FALSE;
2770 }
2771
2772 /***********************************************************************
2773 * CreateUrlCacheEntryA (WININET.@)
2774 *
2775 */
2776 BOOL WINAPI CreateUrlCacheEntryA(LPCSTR lpszUrlName, DWORD dwExpectedFileSize,
2777 LPCSTR lpszFileExtension, LPSTR lpszFileName, DWORD dwReserved)
2778 {
2779 WCHAR file_name[MAX_PATH];
2780
2781 if(dwReserved)
2782 FIXME("dwReserved 0x%08x\n", dwReserved);
2783
2784 if(!urlcache_entry_create(lpszUrlName, lpszFileExtension, file_name))
2785 return FALSE;
2786
2787 if(!WideCharToMultiByte(CP_ACP, 0, file_name, -1, lpszFileName, MAX_PATH, NULL, NULL))
2788 return FALSE;
2789 return TRUE;
2790 }
2791 /***********************************************************************
2792 * CreateUrlCacheEntryW (WININET.@)
2793 *
2794 */
2795 BOOL WINAPI CreateUrlCacheEntryW(LPCWSTR lpszUrlName, DWORD dwExpectedFileSize,
2796 LPCWSTR lpszFileExtension, LPWSTR lpszFileName, DWORD dwReserved)
2797 {
2798 char *url, *ext = NULL;
2799 BOOL ret;
2800
2801 if(dwReserved)
2802 FIXME("dwReserved 0x%08x\n", dwReserved);
2803
2804 if(lpszFileExtension) {
2805 ext = heap_strdupWtoUTF8(lpszFileExtension);
2806 if(!ext)
2807 return FALSE;
2808 }
2809
2810 if(!urlcache_encode_url_alloc(lpszUrlName, &url)) {
2811 heap_free(ext);
2812 return FALSE;
2813 }
2814
2815 ret = urlcache_entry_create(url, ext, lpszFileName);
2816 heap_free(ext);
2817 heap_free(url);
2818 return ret;
2819 }
2820
2821 static BOOL urlcache_entry_commit(const char *url, const WCHAR *file_name,
2822 FILETIME expire_time, FILETIME modify_time, DWORD entry_type,
2823 BYTE *header_info, DWORD header_size, const char *file_ext,
2824 const char *original_url)
2825 {
2826 cache_container *container;
2827 urlcache_header *header;
2828 struct hash_entry *hash_entry;
2829 entry_header *entry;
2830 entry_url *url_entry;
2831 DWORD url_entry_offset;
2832 DWORD size = DWORD_ALIGN(sizeof(*url_entry));
2833 DWORD file_name_off = 0;
2834 DWORD header_info_off = 0;
2835 DWORD file_ext_off = 0;
2836 WIN32_FILE_ATTRIBUTE_DATA file_attr;
2837 LARGE_INTEGER file_size;
2838 BYTE dir_id;
2839 char file_name_no_container[MAX_PATH];
2840 char *local_file_name = 0;
2841 DWORD hit_rate = 0;
2842 DWORD exempt_delta = 0;
2843 DWORD error;
2844
2845 TRACE("(%s, %s, ..., ..., %x, %p, %d, %s, %s)\n", debugstr_a(url), debugstr_w(file_name),
2846 entry_type, header_info, header_size, debugstr_a(file_ext), debugstr_a(original_url));
2847
2848 if(entry_type & STICKY_CACHE_ENTRY && !file_name) {
2849 SetLastError(ERROR_INVALID_PARAMETER);
2850 return FALSE;
2851 }
2852 if(original_url)
2853 WARN(": original_url ignored\n");
2854
2855 memset(&file_attr, 0, sizeof(file_attr));
2856 if(file_name) {
2857 if(!GetFileAttributesExW(file_name, GetFileExInfoStandard, &file_attr))
2858 return FALSE;
2859 }
2860 file_size.u.LowPart = file_attr.nFileSizeLow;
2861 file_size.u.HighPart = file_attr.nFileSizeHigh;
2862
2863 error = cache_containers_find(url, &container);
2864 if(error != ERROR_SUCCESS) {
2865 SetLastError(error);
2866 return FALSE;
2867 }
2868
2869 error = cache_container_open_index(container, MIN_BLOCK_NO);
2870 if(error != ERROR_SUCCESS) {
2871 SetLastError(error);
2872 return FALSE;
2873 }
2874
2875 if(!(header = cache_container_lock_index(container)))
2876 return FALSE;
2877
2878 if(urlcache_find_hash_entry(header, url, &hash_entry)) {
2879 entry_url *url_entry = (entry_url*)((LPBYTE)header + hash_entry->offset);
2880
2881 if(urlcache_hash_entry_is_locked(hash_entry, url_entry)) {
2882 TRACE("Trying to overwrite locked entry\n");
2883 cache_container_unlock_index(container, header);
2884 SetLastError(ERROR_SHARING_VIOLATION);
2885 return FALSE;
2886 }
2887
2888 hit_rate = url_entry->hit_rate;
2889 exempt_delta = url_entry->exempt_delta;
2890 urlcache_entry_delete(container, header, hash_entry);
2891 }
2892
2893 if(header->dirs_no)
2894 dir_id = 0;
2895 else
2896 dir_id = CACHE_CONTAINER_NO_SUBDIR;
2897
2898 if(file_name) {
2899 BOOL bFound = FALSE;
2900
2901 if(strncmpW(file_name, container->path, lstrlenW(container->path))) {
2902 ERR("path %s must begin with cache content path %s\n", debugstr_w(file_name), debugstr_w(container->path));
2903 cache_container_unlock_index(container, header);
2904 SetLastError(ERROR_INVALID_PARAMETER);
2905 return FALSE;
2906 }
2907
2908 /* skip container path prefix */
2909 file_name += lstrlenW(container->path);
2910
2911 WideCharToMultiByte(CP_ACP, 0, file_name, -1, file_name_no_container, MAX_PATH, NULL, NULL);
2912 local_file_name = file_name_no_container;
2913
2914 if(header->dirs_no) {
2915 for(dir_id = 0; dir_id < header->dirs_no; dir_id++) {
2916 if(!strncmp(header->directory_data[dir_id].name, local_file_name, DIR_LENGTH)) {
2917 bFound = TRUE;
2918 break;
2919 }
2920 }
2921
2922 if(!bFound) {
2923 ERR("cache directory not found in path %s\n", debugstr_w(file_name));
2924 cache_container_unlock_index(container, header);
2925 SetLastError(ERROR_INVALID_PARAMETER);
2926 return FALSE;
2927 }
2928
2929 file_name += DIR_LENGTH + 1;
2930 local_file_name += DIR_LENGTH + 1;
2931 }
2932 }
2933
2934 size = DWORD_ALIGN(size + strlen(url) + 1);
2935 if(file_name) {
2936 file_name_off = size;
2937 size = DWORD_ALIGN(size + strlen(local_file_name) + 1);
2938 }
2939 if(header_info && header_size) {
2940 header_info_off = size;
2941 size = DWORD_ALIGN(size + header_size);
2942 }
2943 if(file_ext && (file_ext_off = strlen(file_ext))) {
2944 DWORD len = file_ext_off;
2945
2946 file_ext_off = size;
2947 size = DWORD_ALIGN(size + len + 1);
2948 }
2949
2950 /* round up to next block */
2951 if(size % BLOCKSIZE) {
2952 size -= size % BLOCKSIZE;
2953 size += BLOCKSIZE;
2954 }
2955
2956 error = urlcache_entry_alloc(header, size / BLOCKSIZE, &entry);
2957 while(error == ERROR_HANDLE_DISK_FULL) {
2958 error = cache_container_clean_index(container, &header);
2959 if(error == ERROR_SUCCESS)
2960 error = urlcache_entry_alloc(header, size / BLOCKSIZE, &entry);
2961 }
2962 if(error != ERROR_SUCCESS) {
2963 cache_container_unlock_index(container, header);
2964 SetLastError(error);
2965 return FALSE;
2966 }
2967
2968 /* FindFirstFreeEntry fills in blocks used */
2969 url_entry = (entry_url *)entry;
2970 url_entry_offset = (LPBYTE)url_entry - (LPBYTE)header;
2971 url_entry->header.signature = URL_SIGNATURE;
2972 url_entry->cache_dir = dir_id;
2973 url_entry->cache_entry_type = entry_type | container->default_entry_type;
2974 url_entry->header_info_size = header_size;
2975 if((entry_type & STICKY_CACHE_ENTRY) && !exempt_delta) {
2976 /* Sticky entries have a default exempt time of one day */
2977 exempt_delta = 86400;
2978 }
2979 url_entry->exempt_delta = exempt_delta;
2980 url_entry->hit_rate = hit_rate+1;
2981 url_entry->file_extension_off = file_ext_off;
2982 url_entry->header_info_off = header_info_off;
2983 url_entry->local_name_off = file_name_off;
2984 url_entry->url_off = DWORD_ALIGN(sizeof(*url_entry));
2985 url_entry->size.QuadPart = file_size.QuadPart;
2986 url_entry->use_count = 0;
2987 GetSystemTimeAsFileTime(&url_entry->access_time);
2988 url_entry->modification_time = modify_time;
2989 file_time_to_dos_date_time(&url_entry->access_time, &url_entry->sync_date, &url_entry->sync_time);
2990 file_time_to_dos_date_time(&expire_time, &url_entry->expire_date, &url_entry->expire_time);
2991 file_time_to_dos_date_time(&file_attr.ftLastWriteTime, &url_entry->write_date, &url_entry->write_time);
2992
2993 /*** Unknowns ***/
2994 url_entry->unk1 = 0;
2995 url_entry->unk2 = 0;
2996 url_entry->unk3 = 0x60;
2997 url_entry->unk4 = 0;
2998 url_entry->unk5 = 0x1010;
2999 url_entry->unk7 = 0;
3000 url_entry->unk8 = 0;
3001
3002
3003 strcpy((LPSTR)url_entry + url_entry->url_off, url);
3004 if(file_name_off)
3005 strcpy((LPSTR)((LPBYTE)url_entry + file_name_off), local_file_name);
3006 if(header_info_off)
3007 memcpy((LPBYTE)url_entry + header_info_off, header_info, header_size);
3008 if(file_ext_off)
3009 strcpy((LPSTR)((LPBYTE)url_entry + file_ext_off), file_ext);
3010
3011 error = urlcache_hash_entry_create(header, url, url_entry_offset, HASHTABLE_URL);
3012 while(error == ERROR_HANDLE_DISK_FULL) {
3013 error = cache_container_clean_index(container, &header);
3014 if(error == ERROR_SUCCESS) {
3015 url_entry = (entry_url *)((LPBYTE)header + url_entry_offset);
3016 error = urlcache_hash_entry_create(header, url,
3017 url_entry_offset, HASHTABLE_URL);
3018 }
3019 }
3020 if(error != ERROR_SUCCESS) {
3021 urlcache_entry_free(header, &url_entry->header);
3022 cache_container_unlock_index(container, header);
3023 SetLastError(error);
3024 return FALSE;
3025 }
3026
3027 if(url_entry->cache_dir < header->dirs_no)
3028 header->directory_data[url_entry->cache_dir].files_no++;
3029 if(entry_type & STICKY_CACHE_ENTRY)
3030 header->exempt_usage.QuadPart += file_size.QuadPart;
3031 else
3032 header->cache_usage.QuadPart += file_size.QuadPart;
3033 if(header->cache_usage.QuadPart+header->exempt_usage.QuadPart > header->cache_limit.QuadPart)
3034 handle_full_cache();
3035
3036 cache_container_unlock_index(container, header);
3037 return TRUE;
3038 }
3039
3040 /***********************************************************************
3041 * CommitUrlCacheEntryA (WININET.@)
3042 */
3043 BOOL WINAPI CommitUrlCacheEntryA(LPCSTR lpszUrlName, LPCSTR lpszLocalFileName,
3044 FILETIME ExpireTime, FILETIME LastModifiedTime, DWORD CacheEntryType,
3045 LPBYTE lpHeaderInfo, DWORD dwHeaderSize, LPCSTR lpszFileExtension, LPCSTR lpszOriginalUrl)
3046 {
3047 WCHAR *file_name = NULL;
3048 BOOL ret;
3049
3050 if(lpszLocalFileName) {
3051 file_name = heap_strdupAtoW(lpszLocalFileName);
3052 if(!file_name)
3053 return FALSE;
3054 }
3055
3056 ret = urlcache_entry_commit(lpszUrlName, file_name, ExpireTime, LastModifiedTime,
3057 CacheEntryType, lpHeaderInfo, dwHeaderSize, lpszFileExtension, lpszOriginalUrl);
3058 heap_free(file_name);
3059 return ret;
3060 }
3061
3062 /***********************************************************************
3063 * CommitUrlCacheEntryW (WININET.@)
3064 */
3065 BOOL WINAPI CommitUrlCacheEntryW(LPCWSTR lpszUrlName, LPCWSTR lpszLocalFileName,
3066 FILETIME ExpireTime, FILETIME LastModifiedTime, DWORD CacheEntryType,
3067 LPWSTR lpHeaderInfo, DWORD dwHeaderSize, LPCWSTR lpszFileExtension, LPCWSTR lpszOriginalUrl)
3068 {
3069 char *url, *original_url=NULL, *file_ext=NULL, *header_info=NULL;
3070 BOOL ret;
3071
3072 if(!urlcache_encode_url_alloc(lpszUrlName, &url))
3073 return FALSE;
3074
3075 if(lpHeaderInfo) {
3076 header_info = heap_strdupWtoUTF8(lpHeaderInfo);
3077 if(!header_info) {
3078 heap_free(url);
3079 return FALSE;
3080 }
3081 dwHeaderSize = strlen(header_info);
3082 }
3083
3084 if(lpszFileExtension) {
3085 file_ext = heap_strdupWtoA(lpszFileExtension);
3086 if(!file_ext) {
3087 heap_free(url);
3088 heap_free(header_info);
3089 return FALSE;
3090 }
3091 }
3092
3093 if(lpszOriginalUrl && !urlcache_encode_url_alloc(lpszOriginalUrl, &original_url)) {
3094 heap_free(url);
3095 heap_free(header_info);
3096 heap_free(file_ext);
3097 return FALSE;
3098 }
3099
3100 ret = urlcache_entry_commit(url, lpszLocalFileName, ExpireTime, LastModifiedTime,
3101 CacheEntryType, (BYTE*)header_info, dwHeaderSize, file_ext, original_url);
3102 heap_free(url);
3103 heap_free(header_info);
3104 heap_free(file_ext);
3105 heap_free(original_url);
3106 return ret;
3107 }
3108
3109 /***********************************************************************
3110 * ReadUrlCacheEntryStream (WININET.@)
3111 *
3112 */
3113 BOOL WINAPI ReadUrlCacheEntryStream(
3114 IN HANDLE hUrlCacheStream,
3115 IN DWORD dwLocation,
3116 IN OUT LPVOID lpBuffer,
3117 IN OUT LPDWORD lpdwLen,
3118 IN DWORD dwReserved
3119 )
3120 {
3121 /* Get handle to file from 'stream' */
3122 stream_handle *pStream = (stream_handle*)hUrlCacheStream;
3123
3124 if (dwReserved != 0)
3125 {
3126 ERR("dwReserved != 0\n");
3127 SetLastError(ERROR_INVALID_PARAMETER);
3128 return FALSE;
3129 }
3130
3131 if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->url, INTERNET_MAX_URL_LENGTH))
3132 {
3133 SetLastError(ERROR_INVALID_HANDLE);
3134 return FALSE;
3135 }
3136
3137 if (SetFilePointer(pStream->file, dwLocation, NULL, FILE_CURRENT) == INVALID_SET_FILE_POINTER)
3138 return FALSE;
3139 return ReadFile(pStream->file, lpBuffer, *lpdwLen, lpdwLen, NULL);
3140 }
3141
3142 /***********************************************************************
3143 * RetrieveUrlCacheEntryStreamA (WININET.@)
3144 *
3145 */
3146 HANDLE WINAPI RetrieveUrlCacheEntryStreamA(LPCSTR lpszUrlName,
3147 LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
3148 LPDWORD lpdwCacheEntryInfoBufferSize, BOOL fRandomRead, DWORD dwReserved)
3149 {
3150 /* NOTE: this is not the same as the way that the native
3151 * version allocates 'stream' handles. I did it this way
3152 * as it is much easier and no applications should depend
3153 * on this behaviour. (Native version appears to allocate
3154 * indices into a table)
3155 */
3156 stream_handle *stream;
3157 HANDLE file;
3158
3159 TRACE("(%s, %p, %p, %x, 0x%08x)\n", debugstr_a(lpszUrlName), lpCacheEntryInfo,
3160 lpdwCacheEntryInfoBufferSize, fRandomRead, dwReserved);
3161
3162 if(!RetrieveUrlCacheEntryFileA(lpszUrlName, lpCacheEntryInfo,
3163 lpdwCacheEntryInfoBufferSize, dwReserved))
3164 return NULL;
3165
3166 file = CreateFileA(lpCacheEntryInfo->lpszLocalFileName, GENERIC_READ, FILE_SHARE_READ,
3167 NULL, OPEN_EXISTING, fRandomRead ? FILE_FLAG_RANDOM_ACCESS : 0, NULL);
3168 if(file == INVALID_HANDLE_VALUE) {
3169 UnlockUrlCacheEntryFileA(lpszUrlName, 0);
3170 return NULL;
3171 }
3172
3173 /* allocate handle storage space */
3174 stream = heap_alloc(sizeof(stream_handle) + strlen(lpszUrlName) * sizeof(CHAR));
3175 if(!stream) {
3176 CloseHandle(file);
3177 UnlockUrlCacheEntryFileA(lpszUrlName, 0);
3178 SetLastError(ERROR_OUTOFMEMORY);
3179 return NULL;
3180 }
3181
3182 stream->file = file;
3183 strcpy(stream->url, lpszUrlName);
3184 return stream;
3185 }
3186
3187 /***********************************************************************
3188 * RetrieveUrlCacheEntryStreamW (WININET.@)
3189 *
3190 */
3191 HANDLE WINAPI RetrieveUrlCacheEntryStreamW(LPCWSTR lpszUrlName,
3192 LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
3193 LPDWORD lpdwCacheEntryInfoBufferSize, BOOL fRandomRead, DWORD dwReserved)
3194 {
3195 DWORD len;
3196 /* NOTE: this is not the same as the way that the native
3197 * version allocates 'stream' handles. I did it this way
3198 * as it is much easier and no applications should depend
3199 * on this behaviour. (Native version appears to allocate
3200 * indices into a table)
3201 */
3202 stream_handle *stream;
3203 HANDLE file;
3204
3205 TRACE("(%s, %p, %p, %x, 0x%08x)\n", debugstr_w(lpszUrlName), lpCacheEntryInfo,
3206 lpdwCacheEntryInfoBufferSize, fRandomRead, dwReserved);
3207
3208 if(!(len = urlcache_encode_url(lpszUrlName, NULL, 0)))
3209 return NULL;
3210
3211 if(!RetrieveUrlCacheEntryFileW(lpszUrlName, lpCacheEntryInfo,
3212 lpdwCacheEntryInfoBufferSize, dwReserved))
3213 return NULL;
3214
3215 file = CreateFileW(lpCacheEntryInfo->lpszLocalFileName, GENERIC_READ, FILE_SHARE_READ,
3216 NULL, OPEN_EXISTING, fRandomRead ? FILE_FLAG_RANDOM_ACCESS : 0, NULL);
3217 if(file == INVALID_HANDLE_VALUE) {
3218 UnlockUrlCacheEntryFileW(lpszUrlName, 0);
3219 return NULL;
3220 }
3221
3222 /* allocate handle storage space */
3223 stream = heap_alloc(sizeof(stream_handle) + len*sizeof(WCHAR));
3224 if(!stream) {
3225 CloseHandle(file);
3226 UnlockUrlCacheEntryFileW(lpszUrlName, 0);
3227 SetLastError(ERROR_OUTOFMEMORY);
3228 return NULL;
3229 }
3230
3231 stream->file = file;
3232 if(!urlcache_encode_url(lpszUrlName, stream->url, len)) {
3233 CloseHandle(file);
3234 UnlockUrlCacheEntryFileW(lpszUrlName, 0);
3235 heap_free(stream);
3236 return NULL;
3237 }
3238 return stream;
3239 }
3240
3241 /***********************************************************************
3242 * UnlockUrlCacheEntryStream (WININET.@)
3243 *
3244 */
3245 BOOL WINAPI UnlockUrlCacheEntryStream(
3246 IN HANDLE hUrlCacheStream,
3247 IN DWORD dwReserved
3248 )
3249 {
3250 stream_handle *pStream = (stream_handle*)hUrlCacheStream;
3251
3252 if (dwReserved != 0)
3253 {
3254 ERR("dwReserved != 0\n");
3255 SetLastError(ERROR_INVALID_PARAMETER);
3256 return FALSE;
3257 }
3258
3259 if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->url, INTERNET_MAX_URL_LENGTH))
3260 {
3261 SetLastError(ERROR_INVALID_HANDLE);
3262 return FALSE;
3263 }
3264
3265 if (!UnlockUrlCacheEntryFileA(pStream->url, 0))
3266 return FALSE;
3267
3268 CloseHandle(pStream->file);
3269 heap_free(pStream);
3270 return TRUE;
3271 }
3272
3273
3274 /***********************************************************************
3275 * DeleteUrlCacheEntryA (WININET.@)
3276 *
3277 */
3278 BOOL WINAPI DeleteUrlCacheEntryA(LPCSTR lpszUrlName)
3279 {
3280 cache_container *pContainer;
3281 urlcache_header *pHeader;
3282 struct hash_entry *pHashEntry;
3283 DWORD error;
3284 BOOL ret;
3285
3286 TRACE("(%s)\n", debugstr_a(lpszUrlName));
3287
3288 error = cache_containers_find(lpszUrlName, &pContainer);
3289 if (error != ERROR_SUCCESS)
3290 {
3291 SetLastError(error);
3292 return FALSE;
3293 }
3294
3295 error = cache_container_open_index(pContainer, MIN_BLOCK_NO);
3296 if (error != ERROR_SUCCESS)
3297 {
3298 SetLastError(error);
3299 return FALSE;
3300 }
3301
3302 if (!(pHeader = cache_container_lock_index(pContainer)))
3303 return FALSE;
3304
3305 if (!urlcache_find_hash_entry(pHeader, lpszUrlName, &pHashEntry))
3306 {
3307 cache_container_unlock_index(pContainer, pHeader);
3308 TRACE("entry %s not found!\n", lpszUrlName);
3309 SetLastError(ERROR_FILE_NOT_FOUND);
3310 return FALSE;
3311 }
3312
3313 ret = urlcache_entry_delete(pContainer, pHeader, pHashEntry);
3314
3315 cache_container_unlock_index(pContainer, pHeader);
3316
3317 return ret;
3318 }
3319
3320 /***********************************************************************
3321 * DeleteUrlCacheEntryW (WININET.@)
3322 *
3323 */
3324 BOOL WINAPI DeleteUrlCacheEntryW(LPCWSTR lpszUrlName)
3325 {
3326 char *url;
3327 BOOL ret;
3328
3329 if(!urlcache_encode_url_alloc(lpszUrlName, &url))
3330 return FALSE;
3331
3332 ret = DeleteUrlCacheEntryA(url);
3333 heap_free(url);
3334 return ret;
3335 }
3336
3337 BOOL WINAPI DeleteUrlCacheContainerA(DWORD d1, DWORD d2)
3338 {
3339 FIXME("(0x%08x, 0x%08x) stub\n", d1, d2);
3340 return TRUE;
3341 }
3342
3343 BOOL WINAPI DeleteUrlCacheContainerW(DWORD d1, DWORD d2)
3344 {
3345 FIXME("(0x%08x, 0x%08x) stub\n", d1, d2);
3346 return TRUE;
3347 }
3348
3349 /***********************************************************************
3350 * CreateCacheContainerA (WININET.@)
3351 */
3352 BOOL WINAPI CreateUrlCacheContainerA(DWORD d1, DWORD d2, DWORD d3, DWORD d4,
3353 DWORD d5, DWORD d6, DWORD d7, DWORD d8)
3354 {
3355 FIXME("(0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x) stub\n",
3356 d1, d2, d3, d4, d5, d6, d7, d8);
3357 return TRUE;
3358 }
3359
3360 /***********************************************************************
3361 * CreateCacheContainerW (WININET.@)
3362 */
3363 BOOL WINAPI CreateUrlCacheContainerW(DWORD d1, DWORD d2, DWORD d3, DWORD d4,
3364 DWORD d5, DWORD d6, DWORD d7, DWORD d8)
3365 {
3366 FIXME("(0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x) stub\n",
3367 d1, d2, d3, d4, d5, d6, d7, d8);
3368 return TRUE;
3369 }
3370
3371 /***********************************************************************
3372 * FindFirstUrlCacheContainerA (WININET.@)
3373 */
3374 HANDLE WINAPI FindFirstUrlCacheContainerA( LPVOID p1, LPVOID p2, LPVOID p3, DWORD d1 )
3375 {
3376 FIXME("(%p, %p, %p, 0x%08x) stub\n", p1, p2, p3, d1 );
3377 return NULL;
3378 }
3379
3380 /***********************************************************************
3381 * FindFirstUrlCacheContainerW (WININET.@)
3382 */
3383 HANDLE WINAPI FindFirstUrlCacheContainerW( LPVOID p1, LPVOID p2, LPVOID p3, DWORD d1 )
3384 {
3385 FIXME("(%p, %p, %p, 0x%08x) stub\n", p1, p2, p3, d1 );
3386 return NULL;
3387 }
3388
3389 /***********************************************************************
3390 * FindNextUrlCacheContainerA (WININET.@)
3391 */
3392 BOOL WINAPI FindNextUrlCacheContainerA( HANDLE handle, LPVOID p1, LPVOID p2 )
3393 {
3394 FIXME("(%p, %p, %p) stub\n", handle, p1, p2 );
3395 return FALSE;
3396 }
3397
3398 /***********************************************************************
3399 * FindNextUrlCacheContainerW (WININET.@)
3400 */
3401 BOOL WINAPI FindNextUrlCacheContainerW( HANDLE handle, LPVOID p1, LPVOID p2 )
3402 {
3403 FIXME("(%p, %p, %p) stub\n", handle, p1, p2 );
3404 return FALSE;
3405 }
3406
3407 HANDLE WINAPI FindFirstUrlCacheEntryExA(
3408 LPCSTR lpszUrlSearchPattern,
3409 DWORD dwFlags,
3410 DWORD dwFilter,
3411 GROUPID GroupId,
3412 LPINTERNET_CACHE_ENTRY_INFOA lpFirstCacheEntryInfo,
3413 LPDWORD lpdwFirstCacheEntryInfoBufferSize,
3414 LPVOID lpReserved,
3415 LPDWORD pcbReserved2,
3416 LPVOID lpReserved3
3417 )
3418 {
3419 FIXME("(%s, 0x%08x, 0x%08x, 0x%08x%08x, %p, %p, %p, %p, %p) stub\n", debugstr_a(lpszUrlSearchPattern),
3420 dwFlags, dwFilter, (ULONG)(GroupId >> 32), (ULONG)GroupId, lpFirstCacheEntryInfo,
3421 lpdwFirstCacheEntryInfoBufferSize, lpReserved, pcbReserved2,lpReserved3);
3422 SetLastError(ERROR_FILE_NOT_FOUND);
3423 return NULL;
3424 }
3425
3426 HANDLE WINAPI FindFirstUrlCacheEntryExW(
3427 LPCWSTR lpszUrlSearchPattern,
3428 DWORD dwFlags,
3429 DWORD dwFilter,
3430 GROUPID GroupId,
3431 LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo,
3432 LPDWORD lpdwFirstCacheEntryInfoBufferSize,
3433 LPVOID lpReserved,
3434 LPDWORD pcbReserved2,
3435 LPVOID lpReserved3
3436 )
3437 {
3438 FIXME("(%s, 0x%08x, 0x%08x, 0x%08x%08x, %p, %p, %p, %p, %p) stub\n", debugstr_w(lpszUrlSearchPattern),
3439 dwFlags, dwFilter, (ULONG)(GroupId >> 32), (ULONG)GroupId, lpFirstCacheEntryInfo,
3440 lpdwFirstCacheEntryInfoBufferSize, lpReserved, pcbReserved2,lpReserved3);
3441 SetLastError(ERROR_FILE_NOT_FOUND);
3442 return NULL;
3443 }
3444
3445 /***********************************************************************
3446 * FindFirstUrlCacheEntryA (WININET.@)
3447 *
3448 */
3449 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryA(LPCSTR lpszUrlSearchPattern,
3450 LPINTERNET_CACHE_ENTRY_INFOA lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
3451 {
3452 find_handle *pEntryHandle;
3453
3454 TRACE("(%s, %p, %p)\n", debugstr_a(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
3455
3456 pEntryHandle = heap_alloc(sizeof(*pEntryHandle));
3457 if (!pEntryHandle)
3458 return NULL;
3459
3460 pEntryHandle->magic = URLCACHE_FIND_ENTRY_HANDLE_MAGIC;
3461 if (lpszUrlSearchPattern)
3462 {
3463 pEntryHandle->url_search_pattern = heap_strdupA(lpszUrlSearchPattern);
3464 if (!pEntryHandle->url_search_pattern)
3465 {
3466 heap_free(pEntryHandle);
3467 return NULL;
3468 }
3469 }
3470 else
3471 pEntryHandle->url_search_pattern = NULL;
3472 pEntryHandle->container_idx = 0;
3473 pEntryHandle->hash_table_idx = 0;
3474 pEntryHandle->hash_entry_idx = 0;
3475
3476 if (!FindNextUrlCacheEntryA(pEntryHandle, lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize))
3477 {
3478 heap_free(pEntryHandle);
3479 return NULL;
3480 }
3481 return pEntryHandle;
3482 }
3483
3484 /***********************************************************************
3485 * FindFirstUrlCacheEntryW (WININET.@)
3486 *
3487 */
3488 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryW(LPCWSTR lpszUrlSearchPattern,
3489 LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
3490 {
3491 find_handle *pEntryHandle;
3492
3493 TRACE("(%s, %p, %p)\n", debugstr_w(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
3494
3495 pEntryHandle = heap_alloc(sizeof(*pEntryHandle));
3496 if (!pEntryHandle)
3497 return NULL;
3498
3499 pEntryHandle->magic = URLCACHE_FIND_ENTRY_HANDLE_MAGIC;
3500 if (lpszUrlSearchPattern)
3501 {
3502 pEntryHandle->url_search_pattern = heap_strdupWtoA(lpszUrlSearchPattern);
3503 if (!pEntryHandle->url_search_pattern)
3504 {
3505 heap_free(pEntryHandle);
3506 return NULL;
3507 }
3508 }
3509 else
3510 pEntryHandle->url_search_pattern = NULL;
3511 pEntryHandle->container_idx = 0;
3512 pEntryHandle->hash_table_idx = 0;
3513 pEntryHandle->hash_entry_idx = 0;
3514
3515 if (!FindNextUrlCacheEntryW(pEntryHandle, lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize))
3516 {
3517 heap_free(pEntryHandle);
3518 return NULL;
3519 }
3520 return pEntryHandle;
3521 }
3522
3523 static BOOL urlcache_find_next_entry(
3524 HANDLE hEnumHandl