Apparently this portion of the test was failing under some versions of
[wine] / dlls / wininet / urlcache.c
1 /*
2  * Wininet - Url Cache functions
3  *
4  * Copyright 2001,2002 CodeWeavers
5  * Copyright 2003 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24
25 #define COM_NO_WINDOWS_H
26 #include "config.h"
27 #include "wine/port.h"
28
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34
35 #include "windef.h"
36 #include "winbase.h"
37 #include "winuser.h"
38 #include "wininet.h"
39 #include "winerror.h"
40 #include "internet.h"
41 #include "winreg.h"
42 #include "shlwapi.h"
43 #include "wingdi.h"
44 #include "shlobj.h"
45
46 #include "wine/unicode.h"
47 #include "wine/list.h"
48 #include "wine/debug.h"
49
50 WINE_DEFAULT_DEBUG_CHANNEL(wininet);
51
52 #define ENTRY_START_OFFSET  0x4000
53 #define DIR_LENGTH          8
54 #define BLOCKSIZE           128
55 #define HASHTABLE_SIZE      448
56 #define HASHTABLE_BLOCKSIZE 7
57 #define HASHTABLE_FREE      3
58 #define ALLOCATION_TABLE_OFFSET 0x250
59 #define ALLOCATION_TABLE_SIZE   (0x1000 - ALLOCATION_TABLE_OFFSET)
60 #define HASHTABLE_NUM_ENTRIES   (HASHTABLE_SIZE / HASHTABLE_BLOCKSIZE)
61
62 #define DWORD_SIG(a,b,c,d)  (a | (b << 8) | (c << 16) | (d << 24))
63 #define URL_SIGNATURE   DWORD_SIG('U','R','L',' ')
64 #define REDR_SIGNATURE  DWORD_SIG('R','E','D','R')
65 #define LEAK_SIGNATURE  DWORD_SIG('L','E','A','K')
66 #define HASH_SIGNATURE  DWORD_SIG('H','A','S','H')
67
68 #define DWORD_ALIGN(x) ( (DWORD)(((DWORD)(x)+sizeof(DWORD)-1)/sizeof(DWORD))*sizeof(DWORD) )
69
70 typedef struct _CACHEFILE_ENTRY
71 {
72 /*  union
73     {*/
74         DWORD dwSignature; /* e.g. "URL " */
75 /*      CHAR szSignature[4];
76     };*/
77     DWORD dwBlocksUsed; /* number of 128byte blocks used by this entry */
78 } CACHEFILE_ENTRY;
79
80 typedef struct _URL_CACHEFILE_ENTRY
81 {
82     CACHEFILE_ENTRY CacheFileEntry;
83     FILETIME LastModifiedTime;
84     FILETIME LastAccessTime;
85     WORD wExpiredDate; /* expire date in dos format */
86     WORD wExpiredTime; /* expire time in dos format */
87     DWORD dwUnknown1; /* usually zero */
88     DWORD dwSizeLow; /* see INTERNET_CACHE_ENTRY_INFO::dwSizeLow */
89     DWORD dwSizeHigh; /* see INTERNET_CACHE_ENTRY_INFO::dwSizeHigh */
90     DWORD dwUnknown2; /* usually zero */
91     DWORD dwExemptDelta; /* see INTERNET_CACHE_ENTRY_INFO::dwExemptDelta */
92     DWORD dwUnknown3; /* usually 0x60 */
93     DWORD dwOffsetUrl; /* usually 0x68 */
94     BYTE CacheDir; /* index of cache directory this url is stored in */
95     BYTE Unknown4; /* usually zero */
96     WORD wUnknown5; /* usually 0x1010 */
97     DWORD dwOffsetLocalName; /* offset of start of local filename from start of entry */
98     DWORD CacheEntryType; /* see INTERNET_CACHE_ENTRY_INFO::CacheEntryType */
99     DWORD dwOffsetHeaderInfo; /* offset of start of header info from start of entry */
100     DWORD dwHeaderInfoSize;
101     DWORD dwUnknown6; /* usually zero */
102     WORD wLastSyncDate; /* last sync date in dos format */
103     WORD wLastSyncTime; /* last sync time in dos format */
104     DWORD dwHitRate; /* see INTERNET_CACHE_ENTRY_INFO::dwHitRate */
105     DWORD dwUseCount; /* see INTERNET_CACHE_ENTRY_INFO::dwUseCount */
106     WORD wUnknownDate; /* usually same as wLastSyncDate */
107     WORD wUnknownTime; /* usually same as wLastSyncTime */
108     DWORD dwUnknown7; /* usually zero */
109     DWORD dwUnknown8; /* usually zero */
110     CHAR szSourceUrlName[1]; /* start of url */
111     /* packing to dword align start of next field */
112     /* CHAR szLocalFileName[]; (local file name exluding path) */
113     /* packing to dword align start of next field */
114     /* CHAR szHeaderInfo[]; (header info) */
115 } URL_CACHEFILE_ENTRY;
116
117 struct _HASH_ENTRY
118 {
119     DWORD dwHashKey;
120     DWORD dwOffsetEntry;
121 };
122
123 typedef struct _HASH_CACHEFILE_ENTRY
124 {
125     CACHEFILE_ENTRY CacheFileEntry;
126     DWORD dwAddressNext;
127     DWORD dwHashTableNumber;
128     struct _HASH_ENTRY HashTable[HASHTABLE_SIZE];
129 } HASH_CACHEFILE_ENTRY;
130
131 typedef struct _DIRECTORY_DATA
132 {
133     DWORD dwUnknown;
134     char filename[DIR_LENGTH];
135 } DIRECTORY_DATA;
136
137 typedef struct _URLCACHE_HEADER
138 {
139     char szSignature[28];
140     DWORD dwFileSize;
141     DWORD dwOffsetFirstHashTable;
142     DWORD dwIndexCapacityInBlocks;
143     DWORD dwBlocksInUse; /* is this right? */
144     DWORD dwUnknown1;
145     DWORD dwCacheLimitLow; /* disk space limit for cache */
146     DWORD dwCacheLimitHigh; /* disk space limit for cache */
147     DWORD dwUnknown4; /* current disk space usage for cache? */
148     DWORD dwUnknown5; /* current disk space usage for cache? */
149     DWORD dwUnknown6; /* possibly a flag? */
150     DWORD dwUnknown7;
151     BYTE DirectoryCount; /* number of directory_data's */
152     BYTE Unknown8[3]; /* just padding? */
153     DIRECTORY_DATA directory_data[1]; /* first directory entry */
154 } URLCACHE_HEADER, *LPURLCACHE_HEADER;
155 typedef const URLCACHE_HEADER *LPCURLCACHE_HEADER;
156
157 typedef struct _STREAM_HANDLE
158 {
159     HANDLE hFile;
160     CHAR lpszUrl[1];
161 } STREAM_HANDLE;
162
163 typedef struct _URLCACHECONTAINER
164 {
165     struct list entry; /* part of a list */
166     LPWSTR cache_prefix; /* string that has to be prefixed for this container to be used */
167     LPWSTR path; /* path to url container directory */
168     HANDLE hMapping; /* handle of file mapping */
169     DWORD file_size; /* size of file when mapping was opened */
170     HANDLE hMutex; /* hande of mutex */
171 } URLCACHECONTAINER;
172
173
174 /* List of all containers available */
175 static struct list UrlContainers = LIST_INIT(UrlContainers);
176
177
178 /***********************************************************************
179  *           URLCache_PathToObjectName (Internal)
180  *
181  *  Converts a path to a name suitable for use as a Win32 object name.
182  * Replaces '\\' characters in-place with the specified character
183  * (usually '_' or '!')
184  *
185  * RETURNS
186  *    nothing
187  *
188  */
189 static void URLCache_PathToObjectName(LPWSTR lpszPath, WCHAR replace)
190 {
191     for (; *lpszPath; lpszPath++)
192     {
193         if (*lpszPath == '\\')
194             *lpszPath = replace;
195     }
196 }
197
198 /***********************************************************************
199  *           URLCacheContainer_OpenIndex (Internal)
200  *
201  *  Opens the index file and saves mapping handle in hCacheIndexMapping
202  *
203  * RETURNS
204  *    TRUE if succeeded
205  *    FALSE if failed
206  *
207  */
208 static BOOL URLCacheContainer_OpenIndex(URLCACHECONTAINER * pContainer)
209 {
210     HANDLE hFile;
211     WCHAR wszFilePath[MAX_PATH];
212     DWORD dwFileSize;
213
214     static const WCHAR wszIndex[] = {'i','n','d','e','x','.','d','a','t',0};
215     static const WCHAR wszMappingFormat[] = {'%','s','%','s','_','%','l','u',0};
216
217     if (pContainer->hMapping)
218         return TRUE;
219
220     strcpyW(wszFilePath, pContainer->path);
221     strcatW(wszFilePath, wszIndex);
222
223     hFile = CreateFileW(wszFilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
224     if (hFile == INVALID_HANDLE_VALUE)
225     {
226         FIXME("need to create cache index file\n");
227         return FALSE;
228     }
229
230     dwFileSize = GetFileSize(hFile, NULL);
231     if (dwFileSize == INVALID_FILE_SIZE)
232         return FALSE;
233
234     if (dwFileSize == 0)
235     {
236         FIXME("need to create cache index file\n");
237         return FALSE;
238     }
239
240     wsprintfW(wszFilePath, wszMappingFormat, pContainer->path, wszIndex, dwFileSize);
241     URLCache_PathToObjectName(wszFilePath, '_');
242     pContainer->hMapping = OpenFileMappingW(FILE_MAP_WRITE, FALSE, wszFilePath);
243     if (!pContainer->hMapping)
244         pContainer->hMapping = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, wszFilePath);
245     CloseHandle(hFile);
246     if (!pContainer->hMapping)
247     {
248         ERR("Couldn't create file mapping (error is %ld)\n", GetLastError());
249         return FALSE;
250     }
251
252     return TRUE;
253 }
254
255 /***********************************************************************
256  *           URLCacheContainer_CloseIndex (Internal)
257  *
258  *  Closes the index
259  *
260  * RETURNS
261  *    nothing
262  *
263  */
264 static void URLCacheContainer_CloseIndex(URLCACHECONTAINER * pContainer)
265 {
266     CloseHandle(pContainer->hMapping);
267     pContainer->hMapping = NULL;
268 }
269
270 static BOOL URLCacheContainers_AddContainer(LPCWSTR cache_prefix, LPCWSTR path, LPWSTR mutex_name)
271 {
272     URLCACHECONTAINER * pContainer = HeapAlloc(GetProcessHeap(), 0, sizeof(URLCACHECONTAINER));
273     int path_len = strlenW(path);
274     int cache_prefix_len = strlenW(cache_prefix);
275
276     if (!pContainer)
277     {
278         return FALSE;
279     }
280
281     pContainer->hMapping = NULL;
282     pContainer->file_size = 0;
283
284     pContainer->path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (path_len + 1) * sizeof(WCHAR));
285     if (!pContainer->path)
286     {
287         HeapFree(GetProcessHeap(), 0, pContainer);
288         return FALSE;
289     }
290
291     memcpy(pContainer->path, path, (path_len + 1) * sizeof(WCHAR));
292
293     pContainer->cache_prefix = HeapAlloc(GetProcessHeap(), 0, (cache_prefix_len + 1) * sizeof(WCHAR));
294     if (!pContainer->cache_prefix)
295     {
296         HeapFree(GetProcessHeap(), 0, pContainer->path);
297         HeapFree(GetProcessHeap(), 0, pContainer);
298         return FALSE;
299     }
300
301     memcpy(pContainer->cache_prefix, cache_prefix, (cache_prefix_len + 1) * sizeof(WCHAR));
302
303     CharLowerW(mutex_name);
304     URLCache_PathToObjectName(mutex_name, '!');
305
306     if ((pContainer->hMutex = CreateMutexW(NULL, FALSE, mutex_name)) == NULL)
307     {
308         ERR("couldn't create mutex (error is %ld)\n", GetLastError());
309         HeapFree(GetProcessHeap(), 0, pContainer->path);
310         HeapFree(GetProcessHeap(), 0, pContainer);
311         return FALSE;
312     }
313
314     list_add_head(&UrlContainers, &pContainer->entry);
315
316     return TRUE;
317 }
318
319 static void URLCacheContainer_DeleteContainer(URLCACHECONTAINER * pContainer)
320 {
321     list_remove(&pContainer->entry);
322
323     URLCacheContainer_CloseIndex(pContainer);
324     CloseHandle(pContainer->hMutex);
325     HeapFree(GetProcessHeap(), 0, pContainer->path);
326     HeapFree(GetProcessHeap(), 0, pContainer->cache_prefix);
327     HeapFree(GetProcessHeap(), 0, pContainer);
328 }
329
330 void URLCacheContainers_CreateDefaults()
331 {
332     static const WCHAR UrlSuffix[] = {'C','o','n','t','e','n','t','.','I','E','5',0};
333     static const WCHAR UrlPrefix[] = {0};
334     static const WCHAR HistorySuffix[] = {'H','i','s','t','o','r','y','.','I','E','5',0};
335     static const WCHAR HistoryPrefix[] = {'V','i','s','i','t','e','d',':',0};
336     static const WCHAR CookieSuffix[] = {0};
337     static const WCHAR CookiePrefix[] = {'C','o','o','k','i','e',':',0};
338     static const struct
339     {
340         int nFolder; /* CSIDL_* constant */
341         const WCHAR * shpath_suffix; /* suffix on path returned by SHGetSpecialFolderPath */
342         const WCHAR * cache_prefix; /* prefix used to reference the container */
343     } DefaultContainerData[] = 
344     {
345         { CSIDL_INTERNET_CACHE, UrlSuffix, UrlPrefix },
346         { CSIDL_HISTORY, HistorySuffix, HistoryPrefix },
347         { CSIDL_COOKIES, CookieSuffix, CookiePrefix },
348     };
349     DWORD i;
350
351     for (i = 0; i < sizeof(DefaultContainerData) / sizeof(DefaultContainerData[0]); i++)
352     {
353         WCHAR wszCachePath[MAX_PATH];
354         WCHAR wszMutexName[MAX_PATH];
355         int path_len, suffix_len;
356
357         if (FAILED(SHGetSpecialFolderPathW(NULL, wszCachePath, DefaultContainerData[i].nFolder, TRUE)))
358         {
359             ERR("Couldn't get path for default container %lu\n", i);
360             continue;
361         }
362         path_len = strlenW(wszCachePath);
363         suffix_len = strlenW(DefaultContainerData[i].shpath_suffix);
364
365         if (path_len + suffix_len + 2 > MAX_PATH)
366         {
367             ERR("Path too long\n");
368             continue;
369         }
370
371         wszCachePath[path_len] = '\\';
372
373         strcpyW(wszMutexName, wszCachePath);
374         
375         if (suffix_len)
376         {
377             memcpy(wszCachePath + path_len + 1, DefaultContainerData[i].shpath_suffix, (suffix_len + 1) * sizeof(WCHAR));
378             wszCachePath[path_len + suffix_len + 1] = '\\';
379             wszCachePath[path_len + suffix_len + 2] = '\0';
380         }
381
382         URLCacheContainers_AddContainer(DefaultContainerData[i].cache_prefix, wszCachePath, wszMutexName);
383     }
384 }
385
386 void URLCacheContainers_DeleteAll()
387 {
388     while(!list_empty(&UrlContainers))
389         URLCacheContainer_DeleteContainer(
390             LIST_ENTRY(list_head(&UrlContainers), URLCACHECONTAINER, entry)
391         );
392 }
393
394 static BOOL URLCacheContainers_FindContainerW(LPCWSTR lpwszUrl, URLCACHECONTAINER ** ppContainer)
395 {
396     struct list * cursor;
397
398     TRACE("searching for prefix for URL: %s\n", debugstr_w(lpwszUrl));
399
400     LIST_FOR_EACH(cursor, &UrlContainers)
401     {
402         URLCACHECONTAINER * pContainer = LIST_ENTRY(cursor, URLCACHECONTAINER, entry);
403         int prefix_len = strlenW(pContainer->cache_prefix);
404         if (!strncmpW(pContainer->cache_prefix, lpwszUrl, prefix_len))
405         {
406             TRACE("found container with prefx %s for URL %s\n", debugstr_w(pContainer->cache_prefix), debugstr_w(lpwszUrl));
407             *ppContainer = pContainer;
408             return TRUE;
409         }
410     }
411     ERR("no container found\n");
412     SetLastError(ERROR_FILE_NOT_FOUND);
413     return FALSE;
414 }
415
416 static BOOL URLCacheContainers_FindContainerA(LPCSTR lpszUrl, URLCACHECONTAINER ** ppContainer)
417 {
418     BOOL ret;
419     LPWSTR lpwszUrl;
420     int url_len = MultiByteToWideChar(CP_ACP, 0, lpszUrl, -1, NULL, 0);
421     if (url_len && (lpwszUrl = HeapAlloc(GetProcessHeap(), 0, url_len * sizeof(WCHAR))))
422     {
423         MultiByteToWideChar(CP_ACP, 0, lpszUrl, -1, lpwszUrl, url_len);
424         ret = URLCacheContainers_FindContainerW(lpwszUrl, ppContainer);
425         HeapFree(GetProcessHeap(), 0, lpwszUrl);
426         return ret;
427     }
428     return FALSE;
429 }
430
431 /***********************************************************************
432  *           URLCacheContainer_LockIndex (Internal)
433  *
434  */
435 static LPURLCACHE_HEADER URLCacheContainer_LockIndex(URLCACHECONTAINER * pContainer)
436 {
437     BYTE index;
438     LPVOID pIndexData;
439     URLCACHE_HEADER * pHeader;
440
441     /* acquire mutex */
442     WaitForSingleObject(pContainer->hMutex, INFINITE);
443
444     pIndexData = MapViewOfFile(pContainer->hMapping, FILE_MAP_WRITE, 0, 0, 0);
445
446     if (!pIndexData)
447     {
448         ReleaseMutex(pContainer->hMutex);
449         ERR("Couldn't MapViewOfFile. Error: %ld\n", GetLastError());
450         return FALSE;
451     }
452     pHeader = (URLCACHE_HEADER *)pIndexData;
453
454     /* file has grown - we need to remap to prevent us getting
455      * access violations when we try and access beyond the end
456      * of the memory mapped file */
457     if (pHeader->dwFileSize != pContainer->file_size)
458     {
459         URLCacheContainer_CloseIndex(pContainer);
460         if (!URLCacheContainer_OpenIndex(pContainer))
461         {
462             ReleaseMutex(pContainer->hMutex);
463             return FALSE;
464         }
465         pIndexData = MapViewOfFile(pContainer->hMapping, FILE_MAP_WRITE, 0, 0, 0);
466
467         if (!pIndexData)
468         {
469             ReleaseMutex(pContainer->hMutex);
470             ERR("Couldn't MapViewOfFile. Error: %ld\n", GetLastError());
471             return FALSE;
472         }
473         pHeader = (URLCACHE_HEADER *)pIndexData;
474     }
475
476     TRACE("Signature: %s, file size: %ld bytes\n", pHeader->szSignature, pHeader->dwFileSize);
477
478     for (index = 0; index < pHeader->DirectoryCount; index++)
479     {
480         TRACE("Directory[%d] = \"%.8s\"\n", index, pHeader->directory_data[index].filename);
481     }
482     
483     return pHeader;
484 }
485
486 /***********************************************************************
487  *           URLCacheContainer_UnlockIndex (Internal)
488  *
489  */
490 static BOOL URLCacheContainer_UnlockIndex(URLCACHECONTAINER * pContainer, LPURLCACHE_HEADER pHeader)
491 {
492     /* release mutex */
493     ReleaseMutex(pContainer->hMutex);
494     return UnmapViewOfFile(pHeader);
495 }
496
497
498 #ifndef CHAR_BIT
499 #define CHAR_BIT    (8 * sizeof(CHAR))
500 #endif
501
502 /***********************************************************************
503  *           URLCache_Allocation_BlockIsFree (Internal)
504  *
505  *  Is the specified block number free?
506  *
507  * RETURNS
508  *    zero if free
509  *    non-zero otherwise
510  *
511  */
512 static inline BYTE URLCache_Allocation_BlockIsFree(BYTE * AllocationTable, DWORD dwBlockNumber)
513 {
514     BYTE mask = 1 << (dwBlockNumber % CHAR_BIT);
515     return (AllocationTable[dwBlockNumber / CHAR_BIT] & mask) == 0;
516 }
517
518 /***********************************************************************
519  *           URLCache_Allocation_BlockFree (Internal)
520  *
521  *  Marks the specified block as free
522  *
523  * RETURNS
524  *    nothing
525  *
526  */
527 static inline void URLCache_Allocation_BlockFree(BYTE * AllocationTable, DWORD dwBlockNumber)
528 {
529     BYTE mask = ~(1 << (dwBlockNumber % CHAR_BIT));
530     AllocationTable[dwBlockNumber / CHAR_BIT] &= mask;
531 }
532
533 /***********************************************************************
534  *           URLCache_Allocation_BlockAllocate (Internal)
535  *
536  *  Marks the specified block as allocated
537  *
538  * RETURNS
539  *    nothing
540  *
541  */
542 static inline void URLCache_Allocation_BlockAllocate(BYTE * AllocationTable, DWORD dwBlockNumber)
543 {
544     BYTE mask = 1 << (dwBlockNumber % CHAR_BIT);
545     AllocationTable[dwBlockNumber / CHAR_BIT] |= mask;
546 }
547
548 /***********************************************************************
549  *           URLCache_FindFirstFreeEntry (Internal)
550  *
551  *  Finds and allocates the first block of free space big enough and
552  * sets ppEntry to point to it.
553  *
554  * RETURNS
555  *    TRUE if it had enough space
556  *    FALSE if it couldn't find enough space
557  *
558  */
559 static BOOL URLCache_FindFirstFreeEntry(URLCACHE_HEADER * pHeader, DWORD dwBlocksNeeded, CACHEFILE_ENTRY ** ppEntry)
560 {
561     LPBYTE AllocationTable = (LPBYTE)pHeader + ALLOCATION_TABLE_OFFSET;
562     DWORD dwBlockNumber;
563     DWORD dwFreeCounter;
564     for (dwBlockNumber = 0; dwBlockNumber < pHeader->dwIndexCapacityInBlocks; dwBlockNumber++)
565     {
566         for (dwFreeCounter = 0; 
567             dwFreeCounter < dwBlocksNeeded &&
568               dwFreeCounter + dwBlockNumber < pHeader->dwIndexCapacityInBlocks &&
569               URLCache_Allocation_BlockIsFree(AllocationTable, dwBlockNumber + dwFreeCounter);
570             dwFreeCounter++)
571                 TRACE("Found free block at no. %ld (0x%lx)\n", dwBlockNumber, ENTRY_START_OFFSET + dwBlockNumber * BLOCKSIZE);
572
573         if (dwFreeCounter == dwBlocksNeeded)
574         {
575             DWORD index;
576             TRACE("Found free blocks starting at no. %ld (0x%lx)\n", dwBlockNumber, ENTRY_START_OFFSET + dwBlockNumber * BLOCKSIZE);
577             for (index = 0; index < dwBlocksNeeded; index++)
578                 URLCache_Allocation_BlockAllocate(AllocationTable, dwBlockNumber + index);
579             *ppEntry = (CACHEFILE_ENTRY *)((LPBYTE)pHeader + ENTRY_START_OFFSET + dwBlockNumber * BLOCKSIZE);
580             (*ppEntry)->dwBlocksUsed = dwBlocksNeeded;
581             return TRUE;
582         }
583     }
584     FIXME("Grow file\n");
585     return FALSE;
586 }
587
588 /***********************************************************************
589  *           URLCache_DeleteEntry (Internal)
590  *
591  *  Deletes the specified entry and frees the space allocated to it
592  *
593  * RETURNS
594  *    TRUE if it succeeded
595  *    FALSE if it failed
596  *
597  */
598 static BOOL URLCache_DeleteEntry(CACHEFILE_ENTRY * pEntry)
599 {
600     ZeroMemory(pEntry, pEntry->dwBlocksUsed * BLOCKSIZE);
601     return TRUE;
602 }
603
604 /***********************************************************************
605  *           URLCache_LocalFileNameToPathW (Internal)
606  *
607  *  Copies the full path to the specified buffer given the local file
608  * name and the index of the directory it is in. Always sets value in
609  * lpBufferSize to the required buffer size (in bytes).
610  *
611  * RETURNS
612  *    TRUE if the buffer was big enough
613  *    FALSE if the buffer was too small
614  *
615  */
616 static BOOL URLCache_LocalFileNameToPathW(
617     const URLCACHECONTAINER * pContainer,
618     LPCURLCACHE_HEADER pHeader,
619     LPCSTR szLocalFileName,
620     BYTE Directory,
621     LPWSTR wszPath,
622     LPLONG lpBufferSize)
623 {
624     LONG nRequired;
625     int path_len = strlenW(pContainer->path);
626     int file_name_len = MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, NULL, 0);
627     if (Directory >= pHeader->DirectoryCount)
628     {
629         *lpBufferSize = 0;
630         return FALSE;
631     }
632
633     nRequired = (path_len + DIR_LENGTH + file_name_len + 1) * sizeof(WCHAR);
634     if (nRequired < *lpBufferSize)
635     {
636         int dir_len;
637
638         memcpy(wszPath, pContainer->path, path_len * sizeof(WCHAR));
639         dir_len = MultiByteToWideChar(CP_ACP, 0, pHeader->directory_data[Directory].filename, DIR_LENGTH, wszPath + path_len, DIR_LENGTH);
640         wszPath[dir_len + path_len] = '\\';
641         MultiByteToWideChar(CP_ACP, 0, szLocalFileName, -1, wszPath + dir_len + path_len + 1, file_name_len);
642         *lpBufferSize = nRequired;
643         return TRUE;
644     }
645     *lpBufferSize = nRequired;
646     return FALSE;
647 }
648
649 /***********************************************************************
650  *           URLCache_LocalFileNameToPathA (Internal)
651  *
652  *  Copies the full path to the specified buffer given the local file
653  * name and the index of the directory it is in. Always sets value in
654  * lpBufferSize to the required buffer size.
655  *
656  * RETURNS
657  *    TRUE if the buffer was big enough
658  *    FALSE if the buffer was too small
659  *
660  */
661 static BOOL URLCache_LocalFileNameToPathA(
662     const URLCACHECONTAINER * pContainer,
663     LPCURLCACHE_HEADER pHeader,
664     LPCSTR szLocalFileName,
665     BYTE Directory,
666     LPSTR szPath,
667     LPLONG lpBufferSize)
668 {
669     LONG nRequired;
670     int path_len = WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, NULL, 0, NULL, NULL);
671     int file_name_len = strlen(szLocalFileName);
672     int dir_len = DIR_LENGTH;
673     if (Directory >= pHeader->DirectoryCount)
674     {
675         *lpBufferSize = 0;
676         return FALSE;
677     }
678
679     nRequired = (path_len + dir_len + file_name_len + 1) * sizeof(WCHAR);
680     if (nRequired < *lpBufferSize)
681     {
682         WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, szPath, -1, NULL, NULL);
683         strncpy(szPath, pHeader->directory_data[Directory].filename, DIR_LENGTH);
684         szPath[dir_len + path_len] = '\\';
685         strcpy(szPath + dir_len + path_len + 1, szLocalFileName);
686         *lpBufferSize = nRequired;
687         return TRUE;
688     }
689     *lpBufferSize = nRequired;
690     return FALSE;
691 }
692
693 /***********************************************************************
694  *           URLCache_CopyEntry (Internal)
695  *
696  *  Copies an entry from the cache index file to the Win32 structure
697  *
698  * RETURNS
699  *    TRUE if the buffer was big enough
700  *    FALSE if the buffer was too small
701  *
702  */
703 static BOOL URLCache_CopyEntry(
704     URLCACHECONTAINER * pContainer,
705     LPCURLCACHE_HEADER pHeader, 
706     LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo, 
707     LPDWORD lpdwBufferSize, 
708     URL_CACHEFILE_ENTRY * pUrlEntry,
709     BOOL bUnicode)
710 {
711     int lenUrl;
712     DWORD dwRequiredSize = sizeof(*lpCacheEntryInfo);
713
714     if (*lpdwBufferSize >= dwRequiredSize)
715     {
716         lpCacheEntryInfo->lpHeaderInfo = NULL;
717         lpCacheEntryInfo->lpszFileExtension = NULL;
718         lpCacheEntryInfo->lpszLocalFileName = NULL;
719         lpCacheEntryInfo->lpszSourceUrlName = NULL;
720         lpCacheEntryInfo->CacheEntryType = pUrlEntry->CacheEntryType;
721         lpCacheEntryInfo->u.dwExemptDelta = pUrlEntry->dwExemptDelta;
722         lpCacheEntryInfo->dwHeaderInfoSize = pUrlEntry->dwHeaderInfoSize;
723         lpCacheEntryInfo->dwHitRate = pUrlEntry->dwHitRate;
724         lpCacheEntryInfo->dwSizeHigh = pUrlEntry->dwSizeHigh;
725         lpCacheEntryInfo->dwSizeLow = pUrlEntry->dwSizeLow;
726         lpCacheEntryInfo->dwStructSize = sizeof(*lpCacheEntryInfo);
727         lpCacheEntryInfo->dwUseCount = pUrlEntry->dwUseCount;
728         DosDateTimeToFileTime(pUrlEntry->wExpiredDate, pUrlEntry->wExpiredTime, &lpCacheEntryInfo->ExpireTime);
729         lpCacheEntryInfo->LastAccessTime.dwHighDateTime = pUrlEntry->LastAccessTime.dwHighDateTime;
730         lpCacheEntryInfo->LastAccessTime.dwLowDateTime = pUrlEntry->LastAccessTime.dwLowDateTime;
731         lpCacheEntryInfo->LastModifiedTime.dwHighDateTime = pUrlEntry->LastModifiedTime.dwHighDateTime;
732         lpCacheEntryInfo->LastModifiedTime.dwLowDateTime = pUrlEntry->LastModifiedTime.dwLowDateTime;
733         DosDateTimeToFileTime(pUrlEntry->wLastSyncDate, pUrlEntry->wLastSyncTime, &lpCacheEntryInfo->LastSyncTime);
734     }
735
736     if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
737         ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
738     dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
739     if (bUnicode)
740         lenUrl = MultiByteToWideChar(CP_ACP, 0, pUrlEntry->szSourceUrlName, -1, NULL, 0);
741     else
742         lenUrl = strlen(pUrlEntry->szSourceUrlName);
743     dwRequiredSize += lenUrl + 1;
744     
745     /* FIXME: is source url optional? */
746     if (*lpdwBufferSize >= dwRequiredSize)
747     {
748         lpCacheEntryInfo->lpszSourceUrlName = (LPSTR)lpCacheEntryInfo + dwRequiredSize - lenUrl - 1;
749         if (bUnicode)
750             MultiByteToWideChar(CP_ACP, 0, pUrlEntry->szSourceUrlName, -1, (LPWSTR)lpCacheEntryInfo->lpszSourceUrlName, lenUrl + 1);
751         else
752             memcpy(lpCacheEntryInfo->lpszSourceUrlName, pUrlEntry->szSourceUrlName, (lenUrl + 1) * sizeof(CHAR));
753     }
754
755     if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
756         ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
757     dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
758
759     if (pUrlEntry->dwOffsetLocalName)
760     {
761         LONG nLocalFilePathSize;
762         LPSTR lpszLocalFileName;
763         lpszLocalFileName = (LPSTR)lpCacheEntryInfo + dwRequiredSize;
764         nLocalFilePathSize = *lpdwBufferSize - dwRequiredSize;
765         if ((bUnicode && URLCache_LocalFileNameToPathW(pContainer, pHeader, (LPCSTR)pUrlEntry + pUrlEntry->dwOffsetLocalName, pUrlEntry->CacheDir, (LPWSTR)lpszLocalFileName, &nLocalFilePathSize)) ||
766             URLCache_LocalFileNameToPathA(pContainer, pHeader, (LPCSTR)pUrlEntry + pUrlEntry->dwOffsetLocalName, pUrlEntry->CacheDir, lpszLocalFileName, &nLocalFilePathSize))
767         {
768             lpCacheEntryInfo->lpszLocalFileName = lpszLocalFileName;
769         }
770         dwRequiredSize += nLocalFilePathSize;
771
772         if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
773             ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
774         dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
775     }
776     dwRequiredSize += pUrlEntry->dwHeaderInfoSize + 1;
777
778     if (*lpdwBufferSize >= dwRequiredSize)
779     {
780         lpCacheEntryInfo->lpHeaderInfo = (LPSTR)lpCacheEntryInfo + dwRequiredSize - pUrlEntry->dwHeaderInfoSize - 1;
781         memcpy(lpCacheEntryInfo->lpHeaderInfo, (LPSTR)pUrlEntry + pUrlEntry->dwOffsetHeaderInfo, pUrlEntry->dwHeaderInfoSize);
782         ((LPBYTE)lpCacheEntryInfo)[dwRequiredSize - 1] = '\0';
783     }
784     if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
785         ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
786     dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
787
788     if (dwRequiredSize > *lpdwBufferSize)
789     {
790         *lpdwBufferSize = dwRequiredSize;
791         SetLastError(ERROR_INSUFFICIENT_BUFFER);
792         return FALSE;
793     }
794     *lpdwBufferSize = dwRequiredSize;
795     return TRUE;
796 }
797
798
799 /***********************************************************************
800  *           URLCache_SetEntryInfo (Internal)
801  *
802  *  Helper for SetUrlCacheEntryInfo{A,W}. Sets fields in URL entry
803  * according the the flags set by dwFieldControl.
804  *
805  * RETURNS
806  *    TRUE if the buffer was big enough
807  *    FALSE if the buffer was too small
808  *
809  */
810 static BOOL URLCache_SetEntryInfo(URL_CACHEFILE_ENTRY * pUrlEntry, const INTERNET_CACHE_ENTRY_INFOW * lpCacheEntryInfo, DWORD dwFieldControl)
811 {
812     if (dwFieldControl & CACHE_ENTRY_ACCTIME_FC)
813         pUrlEntry->LastAccessTime = lpCacheEntryInfo->LastAccessTime;
814     if (dwFieldControl & CACHE_ENTRY_ATTRIBUTE_FC)
815         pUrlEntry->CacheEntryType = lpCacheEntryInfo->CacheEntryType;
816     if (dwFieldControl & CACHE_ENTRY_EXEMPT_DELTA_FC)
817         pUrlEntry->dwExemptDelta = lpCacheEntryInfo->u.dwExemptDelta;
818     if (dwFieldControl & CACHE_ENTRY_EXPTIME_FC)
819         FIXME("CACHE_ENTRY_EXPTIME_FC unimplemented\n");
820     if (dwFieldControl & CACHE_ENTRY_HEADERINFO_FC)
821         FIXME("CACHE_ENTRY_HEADERINFO_FC unimplemented\n");
822     if (dwFieldControl & CACHE_ENTRY_HITRATE_FC)
823         pUrlEntry->dwHitRate = lpCacheEntryInfo->dwHitRate;
824     if (dwFieldControl & CACHE_ENTRY_MODTIME_FC)
825         pUrlEntry->LastModifiedTime = lpCacheEntryInfo->LastModifiedTime;
826     if (dwFieldControl & CACHE_ENTRY_SYNCTIME_FC)
827         FileTimeToDosDateTime(&lpCacheEntryInfo->LastAccessTime, &pUrlEntry->wLastSyncDate, &pUrlEntry->wLastSyncTime);
828
829     return TRUE;
830 }
831
832 /***********************************************************************
833  *           URLCache_HashKey (Internal)
834  *
835  *  Returns the hash key for a given string
836  *
837  * RETURNS
838  *    hash key for the string
839  *
840  */
841 static DWORD URLCache_HashKey(LPCSTR lpszKey)
842 {
843     /* NOTE: this uses the same lookup table as SHLWAPI.UrlHash{A,W}
844      * but the algorithm and result are not the same!
845      */
846     static const unsigned char lookupTable[256] = 
847     {
848         0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77,
849         0x8A, 0xAA, 0x7D, 0x76, 0x1B, 0xE9, 0x8C, 0x33,
850         0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44,
851         0x1E, 0x07, 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41,
852         0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94, 0xDF,
853         0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C,
854         0x0C, 0xB5, 0x67, 0x46, 0x16, 0x3A, 0x4B, 0x4E,
855         0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90,
856         0xB0, 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53,
857         0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6, 0x29, 0xFE,
858         0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58,
859         0x23, 0xCE, 0x5F, 0x74, 0xFC, 0xC0, 0x36, 0xDD,
860         0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
861         0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D,
862         0xA6, 0x50, 0x32, 0x22, 0xAF, 0xC3, 0x64, 0x63,
863         0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD,
864         0x79, 0x40, 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A,
865         0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9, 0xC2,
866         0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B,
867         0x4A, 0x3B, 0x89, 0xE4, 0x6C, 0xBF, 0xE8, 0x8B,
868         0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C,
869         0xFB, 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70,
870         0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB, 0x0D, 0x20,
871         0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B,
872         0xF9, 0xEC, 0x2D, 0xF4, 0x6F, 0xB6, 0x99, 0x88,
873         0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
874         0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72,
875         0xA2, 0x35, 0xA0, 0xD7, 0xCD, 0xB4, 0x2F, 0x6D,
876         0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34,
877         0x3F, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8,
878         0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB, 0x0A,
879         0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1
880     };
881     BYTE key[4];
882     DWORD i;
883     int subscript[sizeof(key) / sizeof(key[0])];
884
885     subscript[0] = *lpszKey;
886     subscript[1] = (char)(*lpszKey + 1);
887     subscript[2] = (char)(*lpszKey + 2);
888     subscript[3] = (char)(*lpszKey + 3);
889
890     for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
891         key[i] = lookupTable[i];
892
893     for (lpszKey++; *lpszKey && ((lpszKey[0] != '/') || (lpszKey[1] != 0)); lpszKey++)
894     {
895         for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
896             key[i] = lookupTable[*lpszKey ^ key[i]];
897     }
898
899     return *(DWORD *)key;
900 }
901
902 static inline HASH_CACHEFILE_ENTRY * URLCache_HashEntryFromOffset(LPCURLCACHE_HEADER pHeader, DWORD dwOffset)
903 {
904     return (HASH_CACHEFILE_ENTRY *)((LPBYTE)pHeader + dwOffset);
905 }
906
907 static BOOL URLCache_FindHash(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl, struct _HASH_ENTRY ** ppHashEntry)
908 {
909     /* structure of hash table:
910      *  448 entries divided into 64 blocks
911      *  each block therefore contains a chain of 7 key/offset pairs
912      * how position in table is calculated:
913      *  1. the url is hashed in helper function
914      *  2. the key % 64 * 8 is the offset
915      *  3. the key in the hash table is the hash key aligned to 64
916      *
917      * note:
918      *  there can be multiple hash tables in the file and the offset to
919      *  the next one is stored in the header of the hash table
920      */
921     DWORD key = URLCache_HashKey(lpszUrl);
922     DWORD offset = (key % HASHTABLE_NUM_ENTRIES) * sizeof(struct _HASH_ENTRY);
923     HASH_CACHEFILE_ENTRY * pHashEntry;
924     DWORD dwHashTableNumber = 0;
925
926     key = (DWORD)(key / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES;
927
928     for (pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHeader->dwOffsetFirstHashTable);
929          ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) >= ENTRY_START_OFFSET) && ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) < pHeader->dwFileSize);
930          pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHashEntry->dwAddressNext))
931     {
932         int i;
933         if (pHashEntry->dwHashTableNumber != dwHashTableNumber++)
934         {
935             ERR("Error: not right hash table number (%ld) expected %ld\n", pHashEntry->dwHashTableNumber, dwHashTableNumber);
936             continue;
937         }
938         /* make sure that it is in fact a hash entry */
939         if (pHashEntry->CacheFileEntry.dwSignature != HASH_SIGNATURE)
940         {
941             ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->CacheFileEntry.dwSignature);
942             continue;
943         }
944
945         for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
946         {
947             struct _HASH_ENTRY * pHashElement = &pHashEntry->HashTable[offset + i];
948             if (key == (DWORD)(pHashElement->dwHashKey / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES)
949             {
950                 /* FIXME: we should make sure that this is the right element
951                  * before returning and claiming that it is. We can do this
952                  * by doing a simple compare between the URL we were given
953                  * and the URL stored in the entry. However, this assumes
954                  * we know the format of all the entries stored in the
955                  * hash table */
956                 *ppHashEntry = pHashElement;
957                 return TRUE;
958             }
959         }
960     }
961     return FALSE;
962 }
963
964 /***********************************************************************
965  *           URLCache_FindEntryInHash (Internal)
966  *
967  *  Searches all the hash tables in the index for the given URL and
968  * returns the entry, if it was found, in ppEntry
969  *
970  * RETURNS
971  *    TRUE if the entry was found
972  *    FALSE if the entry could not be found
973  *
974  */
975 static BOOL URLCache_FindEntryInHash(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl, CACHEFILE_ENTRY ** ppEntry)
976 {
977     struct _HASH_ENTRY * pHashEntry;
978     if (URLCache_FindHash(pHeader, lpszUrl, &pHashEntry))
979     {
980         *ppEntry = (CACHEFILE_ENTRY *)((LPBYTE)pHeader + pHashEntry->dwOffsetEntry);
981         return TRUE;
982     }
983     return FALSE;
984 }
985
986 /***********************************************************************
987  *           URLCache_HashEntrySetUse (Internal)
988  *
989  *  Searches all the hash tables in the index for the given URL and
990  * sets the use count (stored or'ed with key)
991  *
992  * RETURNS
993  *    TRUE if the entry was found
994  *    FALSE if the entry could not be found
995  *
996  */
997 static BOOL URLCache_HashEntrySetUse(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl, DWORD dwUseCount)
998 {
999     struct _HASH_ENTRY * pHashEntry;
1000     if (URLCache_FindHash(pHeader, lpszUrl, &pHashEntry))
1001     {
1002         pHashEntry->dwHashKey = dwUseCount | (DWORD)(pHashEntry->dwHashKey / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES;
1003         return TRUE;
1004     }
1005     return FALSE;
1006 }
1007
1008 /***********************************************************************
1009  *           URLCache_DeleteEntryFromHash (Internal)
1010  *
1011  *  Searches all the hash tables in the index for the given URL and
1012  * then if found deletes the entry.
1013  *
1014  * RETURNS
1015  *    TRUE if the entry was found
1016  *    FALSE if the entry could not be found
1017  *
1018  */
1019 static BOOL URLCache_DeleteEntryFromHash(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl)
1020 {
1021     struct _HASH_ENTRY * pHashEntry;
1022     if (URLCache_FindHash(pHeader, lpszUrl, &pHashEntry))
1023     {
1024         pHashEntry->dwHashKey = HASHTABLE_FREE;
1025         pHashEntry->dwOffsetEntry = HASHTABLE_FREE;
1026         return TRUE;
1027     }
1028     return FALSE;
1029 }
1030
1031 /***********************************************************************
1032  *           URLCache_AddEntryToHash (Internal)
1033  *
1034  *  Searches all the hash tables for a free slot based on the offset
1035  * generated from the hash key. If a free slot is found, the offset and
1036  * key are entered into the hash table.
1037  *
1038  * RETURNS
1039  *    TRUE if the entry was added
1040  *    FALSE if the entry could not be added
1041  *
1042  */
1043 static BOOL URLCache_AddEntryToHash(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl, DWORD dwOffsetEntry)
1044 {
1045     /* see URLCache_FindEntryInHash for structure of hash tables */
1046
1047     DWORD key = URLCache_HashKey(lpszUrl);
1048     DWORD offset = (key % HASHTABLE_NUM_ENTRIES) * sizeof(struct _HASH_ENTRY);
1049     HASH_CACHEFILE_ENTRY * pHashEntry;
1050     DWORD dwHashTableNumber = 0;
1051
1052     key = (DWORD)(key / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES;
1053
1054     for (pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHeader->dwOffsetFirstHashTable);
1055          ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) >= ENTRY_START_OFFSET) && ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) < pHeader->dwFileSize);
1056          pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHashEntry->dwAddressNext))
1057     {
1058         int i;
1059         if (pHashEntry->dwHashTableNumber != dwHashTableNumber++)
1060         {
1061             ERR("not right hash table number (%ld) expected %ld\n", pHashEntry->dwHashTableNumber, dwHashTableNumber);
1062             break;
1063         }
1064         /* make sure that it is in fact a hash entry */
1065         if (pHashEntry->CacheFileEntry.dwSignature != HASH_SIGNATURE)
1066         {
1067             ERR("not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->CacheFileEntry.dwSignature);
1068             break;
1069         }
1070
1071         for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
1072         {
1073             struct _HASH_ENTRY * pHashElement = &pHashEntry->HashTable[offset + i];
1074             if (pHashElement->dwHashKey == HASHTABLE_FREE) /* if the slot is free */
1075             {
1076                 pHashElement->dwHashKey = key;
1077                 pHashElement->dwOffsetEntry = dwOffsetEntry;
1078                 return TRUE;
1079             }
1080         }
1081     }
1082     FIXME("need to create another hash table\n");
1083     return FALSE;
1084 }
1085
1086 /***********************************************************************
1087  *           GetUrlCacheEntryInfoExA (WININET.@)
1088  *
1089  */
1090 BOOL WINAPI GetUrlCacheEntryInfoExA(
1091     LPCSTR lpszUrl,
1092     LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1093     LPDWORD lpdwCacheEntryInfoBufSize,
1094     LPSTR lpszReserved,
1095     LPDWORD lpdwReserved,
1096     LPVOID lpReserved,
1097     DWORD dwFlags)
1098 {
1099     TRACE("(%s, %p, %p, %p, %p, %p, %lx)\n",
1100         debugstr_a(lpszUrl), 
1101         lpCacheEntryInfo,
1102         lpdwCacheEntryInfoBufSize,
1103         lpszReserved,
1104         lpdwReserved,
1105         lpReserved,
1106         dwFlags);
1107
1108     if ((lpszReserved != NULL) ||
1109         (lpdwReserved != NULL) ||
1110         (lpReserved != NULL))
1111     {
1112         ERR("Reserved value was not 0\n");
1113         SetLastError(ERROR_INVALID_PARAMETER);
1114         return FALSE;
1115     }
1116     if (dwFlags != 0)
1117         FIXME("Undocumented flag(s): %lx\n", dwFlags);
1118     return GetUrlCacheEntryInfoA(lpszUrl, lpCacheEntryInfo, lpdwCacheEntryInfoBufSize);
1119 }
1120
1121 /***********************************************************************
1122  *           GetUrlCacheEntryInfoA (WININET.@)
1123  *
1124  */
1125 BOOL WINAPI GetUrlCacheEntryInfoA(
1126     IN LPCSTR lpszUrlName,
1127     IN LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1128     IN OUT LPDWORD lpdwCacheEntryInfoBufferSize
1129 )
1130 {
1131     LPURLCACHE_HEADER pHeader;
1132     CACHEFILE_ENTRY * pEntry;
1133     URL_CACHEFILE_ENTRY * pUrlEntry;
1134     URLCACHECONTAINER * pContainer;
1135
1136     TRACE("(%s, %p, %p)\n", debugstr_a(lpszUrlName), lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize);
1137
1138     if (!URLCacheContainers_FindContainerA(lpszUrlName, &pContainer))
1139         return FALSE;
1140
1141     if (!URLCacheContainer_OpenIndex(pContainer))
1142         return FALSE;
1143
1144     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1145         return FALSE;
1146
1147     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
1148     {
1149         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1150         WARN("entry %s not found!\n", debugstr_a(lpszUrlName));
1151         SetLastError(ERROR_FILE_NOT_FOUND);
1152         return FALSE;
1153     }
1154
1155     if (pEntry->dwSignature != URL_SIGNATURE)
1156     {
1157         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1158         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->dwSignature, sizeof(DWORD)));
1159         SetLastError(ERROR_FILE_NOT_FOUND);
1160         return FALSE;
1161     }
1162
1163     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
1164     TRACE("Found URL: %s\n", debugstr_a(pUrlEntry->szSourceUrlName));
1165     if (pUrlEntry->dwOffsetHeaderInfo)
1166         TRACE("Header info: %s\n", debugstr_a((LPBYTE)pUrlEntry + pUrlEntry->dwOffsetHeaderInfo));
1167
1168     if (!URLCache_CopyEntry(
1169         pContainer,
1170         pHeader,
1171         lpCacheEntryInfo,
1172         lpdwCacheEntryInfoBufferSize,
1173         pUrlEntry,
1174         FALSE /* ANSI */))
1175     {
1176         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1177         return FALSE;
1178     }
1179     TRACE("Local File Name: %s\n", debugstr_a(lpCacheEntryInfo->lpszLocalFileName));
1180
1181     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1182
1183     return TRUE;
1184 }
1185
1186 /***********************************************************************
1187  *           GetUrlCacheEntryInfoW (WININET.@)
1188  *
1189  */
1190 BOOL WINAPI GetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
1191   LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
1192   LPDWORD lpdwCacheEntryInfoBufferSize)
1193 {
1194     LPURLCACHE_HEADER pHeader;
1195     CACHEFILE_ENTRY * pEntry;
1196     URL_CACHEFILE_ENTRY * pUrlEntry;
1197     URLCACHECONTAINER * pContainer;
1198     LPSTR lpszUrlA;
1199     int url_len;
1200
1201     TRACE("(%s, %p, %p)\n", debugstr_w(lpszUrl), lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize);
1202
1203     url_len = WideCharToMultiByte(CP_ACP, 0, lpszUrl, -1, NULL, 0, NULL, NULL);
1204     lpszUrlA = HeapAlloc(GetProcessHeap(), 0, url_len * sizeof(CHAR));
1205     if (!lpszUrlA)
1206     {
1207         SetLastError(ERROR_OUTOFMEMORY);
1208         return FALSE;
1209     }
1210     WideCharToMultiByte(CP_ACP, 0, lpszUrl, -1, lpszUrlA, url_len, NULL, NULL);
1211
1212     if (!URLCacheContainers_FindContainerW(lpszUrl, &pContainer))
1213     {
1214         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1215         return FALSE;
1216     }
1217
1218     if (!URLCacheContainer_OpenIndex(pContainer))
1219     {
1220         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1221         return FALSE;
1222     }
1223
1224     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1225     {
1226         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1227         return FALSE;
1228     }
1229
1230     if (!URLCache_FindEntryInHash(pHeader, lpszUrlA, &pEntry))
1231     {
1232         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1233         WARN("entry %s not found!\n", debugstr_a(lpszUrlA));
1234         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1235         SetLastError(ERROR_FILE_NOT_FOUND);
1236         return FALSE;
1237     }
1238     HeapFree(GetProcessHeap(), 0, lpszUrlA);
1239
1240     if (pEntry->dwSignature != URL_SIGNATURE)
1241     {
1242         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1243         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->dwSignature, sizeof(DWORD)));
1244         SetLastError(ERROR_FILE_NOT_FOUND);
1245         return FALSE;
1246     }
1247
1248     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
1249     TRACE("Found URL: %s\n", debugstr_a(pUrlEntry->szSourceUrlName));
1250     TRACE("Header info: %s\n", debugstr_a((LPBYTE)pUrlEntry + pUrlEntry->dwOffsetHeaderInfo));
1251
1252     if (!URLCache_CopyEntry(
1253         pContainer,
1254         pHeader,
1255         (LPINTERNET_CACHE_ENTRY_INFOA)lpCacheEntryInfo,
1256         lpdwCacheEntryInfoBufferSize,
1257         pUrlEntry,
1258         TRUE /* UNICODE */))
1259     {
1260         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1261         return FALSE;
1262     }
1263     TRACE("Local File Name: %s\n", debugstr_w(lpCacheEntryInfo->lpszLocalFileName));
1264
1265     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1266
1267     return TRUE;
1268 }
1269
1270 /***********************************************************************
1271  *           GetUrlCacheEntryInfoExW (WININET.@)
1272  *
1273  */
1274 BOOL WINAPI GetUrlCacheEntryInfoExW(
1275     LPCWSTR lpszUrl,
1276     LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
1277     LPDWORD lpdwCacheEntryInfoBufSize,
1278     LPWSTR lpszReserved,
1279     LPDWORD lpdwReserved,
1280     LPVOID lpReserved,
1281     DWORD dwFlags)
1282 {
1283     TRACE("(%s, %p, %p, %p, %p, %p, %lx)\n",
1284         debugstr_w(lpszUrl), 
1285         lpCacheEntryInfo,
1286         lpdwCacheEntryInfoBufSize,
1287         lpszReserved,
1288         lpdwReserved,
1289         lpReserved,
1290         dwFlags);
1291
1292     if ((lpszReserved != NULL) ||
1293         (lpdwReserved != NULL) ||
1294         (lpReserved != NULL))
1295     {
1296         ERR("Reserved value was not 0\n");
1297         SetLastError(ERROR_INVALID_PARAMETER);
1298         return FALSE;
1299     }
1300     if (dwFlags != 0)
1301         FIXME("Undocumented flag(s): %lx\n", dwFlags);
1302     return GetUrlCacheEntryInfoW(lpszUrl, lpCacheEntryInfo, lpdwCacheEntryInfoBufSize);
1303 }
1304
1305 /***********************************************************************
1306  *           SetUrlCacheEntryInfoA (WININET.@)
1307  */
1308 BOOL WINAPI SetUrlCacheEntryInfoA(
1309     LPCSTR lpszUrlName,
1310     LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1311     DWORD dwFieldControl)
1312 {
1313     LPURLCACHE_HEADER pHeader;
1314     CACHEFILE_ENTRY * pEntry;
1315     URLCACHECONTAINER * pContainer;
1316
1317     TRACE("(%s, %p, 0x%08lx)\n", debugstr_a(lpszUrlName), lpCacheEntryInfo, dwFieldControl);
1318
1319     if (!URLCacheContainers_FindContainerA(lpszUrlName, &pContainer))
1320         return FALSE;
1321
1322     if (!URLCacheContainer_OpenIndex(pContainer))
1323         return FALSE;
1324
1325     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1326         return FALSE;
1327
1328     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
1329     {
1330         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1331         WARN("entry %s not found!\n", debugstr_a(lpszUrlName));
1332         SetLastError(ERROR_FILE_NOT_FOUND);
1333         return FALSE;
1334     }
1335
1336     if (pEntry->dwSignature != URL_SIGNATURE)
1337     {
1338         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1339         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->dwSignature, sizeof(DWORD)));
1340         SetLastError(ERROR_FILE_NOT_FOUND);
1341         return FALSE;
1342     }
1343
1344     URLCache_SetEntryInfo(
1345         (URL_CACHEFILE_ENTRY *)pEntry,
1346         (const INTERNET_CACHE_ENTRY_INFOW *)lpCacheEntryInfo,
1347         dwFieldControl);
1348
1349     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1350
1351     return TRUE;
1352 }
1353
1354 /***********************************************************************
1355  *           SetUrlCacheEntryInfoW (WININET.@)
1356  */
1357 BOOL WINAPI SetUrlCacheEntryInfoW(LPCWSTR lpszUrl, LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo, DWORD dwFieldControl)
1358 {
1359     LPURLCACHE_HEADER pHeader;
1360     CACHEFILE_ENTRY * pEntry;
1361     URLCACHECONTAINER * pContainer;
1362     LPSTR lpszUrlA;
1363     int url_len;
1364
1365     TRACE("(%s, %p, 0x%08lx)\n", debugstr_w(lpszUrl), lpCacheEntryInfo, dwFieldControl);
1366
1367     url_len = WideCharToMultiByte(CP_ACP, 0, lpszUrl, -1, NULL, 0, NULL, NULL);
1368     lpszUrlA = HeapAlloc(GetProcessHeap(), 0, url_len * sizeof(CHAR));
1369     if (!lpszUrlA)
1370     {
1371         SetLastError(ERROR_OUTOFMEMORY);
1372         return FALSE;
1373     }
1374     WideCharToMultiByte(CP_ACP, 0, lpszUrl, -1, lpszUrlA, url_len, NULL, NULL);
1375
1376     if (!URLCacheContainers_FindContainerW(lpszUrl, &pContainer))
1377     {
1378         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1379         return FALSE;
1380     }
1381
1382     if (!URLCacheContainer_OpenIndex(pContainer))
1383     {
1384         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1385         return FALSE;
1386     }
1387
1388     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1389     {
1390         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1391         return FALSE;
1392     }
1393
1394     if (!URLCache_FindEntryInHash(pHeader, lpszUrlA, &pEntry))
1395     {
1396         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1397         HeapFree(GetProcessHeap(), 0, lpszUrlA);
1398         WARN("entry %s not found!\n", debugstr_a(lpszUrlA));
1399         SetLastError(ERROR_FILE_NOT_FOUND);
1400         return FALSE;
1401     }
1402     HeapFree(GetProcessHeap(), 0, lpszUrlA);
1403
1404     if (pEntry->dwSignature != URL_SIGNATURE)
1405     {
1406         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1407         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->dwSignature, sizeof(DWORD)));
1408         SetLastError(ERROR_FILE_NOT_FOUND);
1409         return FALSE;
1410     }
1411
1412     URLCache_SetEntryInfo(
1413         (URL_CACHEFILE_ENTRY *)pEntry,
1414         lpCacheEntryInfo,
1415         dwFieldControl);
1416
1417     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1418
1419     return TRUE;
1420 }
1421
1422 /***********************************************************************
1423  *           RetrieveUrlCacheEntryFileA (WININET.@)
1424  *
1425  */
1426 BOOL WINAPI RetrieveUrlCacheEntryFileA(
1427     IN LPCSTR lpszUrlName,
1428     OUT LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo, 
1429     IN OUT LPDWORD lpdwCacheEntryInfoBufferSize,
1430     IN DWORD dwReserved
1431     )
1432 {
1433     LPURLCACHE_HEADER pHeader;
1434     CACHEFILE_ENTRY * pEntry;
1435     URL_CACHEFILE_ENTRY * pUrlEntry;
1436     URLCACHECONTAINER * pContainer;
1437
1438     TRACE("(%s, %p, %p, 0x%08lx)\n",
1439         debugstr_a(lpszUrlName),
1440         lpCacheEntryInfo,
1441         lpdwCacheEntryInfoBufferSize,
1442         dwReserved);
1443
1444     if (!URLCacheContainers_FindContainerA(lpszUrlName, &pContainer))
1445         return FALSE;
1446
1447     if (!URLCacheContainer_OpenIndex(pContainer))
1448         return FALSE;
1449
1450     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1451         return FALSE;
1452
1453     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
1454     {
1455         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1456         TRACE("entry %s not found!\n", lpszUrlName);
1457         SetLastError(ERROR_FILE_NOT_FOUND);
1458         return FALSE;
1459     }
1460
1461     if (pEntry->dwSignature != URL_SIGNATURE)
1462     {
1463         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1464         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->dwSignature, sizeof(DWORD)));
1465         SetLastError(ERROR_FILE_NOT_FOUND);
1466         return FALSE;
1467     }
1468
1469     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
1470     TRACE("Found URL: %s\n", pUrlEntry->szSourceUrlName);
1471     TRACE("Header info: %s\n", (LPBYTE)pUrlEntry + pUrlEntry->dwOffsetHeaderInfo);
1472
1473     pUrlEntry->dwHitRate++;
1474     pUrlEntry->dwUseCount++;
1475     URLCache_HashEntrySetUse(pHeader, lpszUrlName, pUrlEntry->dwUseCount);
1476
1477     if (!URLCache_CopyEntry(pContainer, pHeader, lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize, pUrlEntry, FALSE))
1478     {
1479         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1480         return FALSE;
1481     }
1482     TRACE("Local File Name: %s\n", lpCacheEntryInfo->lpszLocalFileName);
1483
1484     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1485
1486     return TRUE;
1487 }
1488
1489 /***********************************************************************
1490  *           UnlockUrlCacheEntryFileA (WININET.@)
1491  *
1492  */
1493 BOOL WINAPI UnlockUrlCacheEntryFileA(
1494     IN LPCSTR lpszUrlName, 
1495     IN DWORD dwReserved
1496     )
1497 {
1498     LPURLCACHE_HEADER pHeader;
1499     CACHEFILE_ENTRY * pEntry;
1500     URL_CACHEFILE_ENTRY * pUrlEntry;
1501     URLCACHECONTAINER * pContainer;
1502
1503     TRACE("(%s, 0x%08lx)\n", debugstr_a(lpszUrlName), dwReserved);
1504
1505     if (dwReserved)
1506     {
1507         ERR("dwReserved != 0\n");
1508         SetLastError(ERROR_INVALID_PARAMETER);
1509         return FALSE;
1510     }
1511
1512     if (!URLCacheContainers_FindContainerA(lpszUrlName, &pContainer))
1513        return FALSE;
1514
1515     if (!URLCacheContainer_OpenIndex(pContainer))
1516         return FALSE;
1517
1518     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1519         return FALSE;
1520
1521     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
1522     {
1523         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1524         TRACE("entry %s not found!\n", lpszUrlName);
1525         SetLastError(ERROR_FILE_NOT_FOUND);
1526         return FALSE;
1527     }
1528
1529     if (pEntry->dwSignature != URL_SIGNATURE)
1530     {
1531         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1532         FIXME("Trying to retrieve entry of unknown format %s\n", debugstr_an((LPSTR)&pEntry->dwSignature, sizeof(DWORD)));
1533         SetLastError(ERROR_FILE_NOT_FOUND);
1534         return FALSE;
1535     }
1536
1537     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
1538
1539     if (pUrlEntry->dwUseCount == 0)
1540     {
1541         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1542         return FALSE;
1543     }
1544     pUrlEntry->dwUseCount--;
1545     URLCache_HashEntrySetUse(pHeader, lpszUrlName, pUrlEntry->dwUseCount);
1546
1547     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1548
1549     return TRUE;
1550 }
1551
1552 /***********************************************************************
1553  *           CreateUrlCacheEntryA (WININET.@)
1554  *
1555  */
1556 BOOL WINAPI CreateUrlCacheEntryA(
1557     IN LPCSTR lpszUrlName,
1558     IN DWORD dwExpectedFileSize,
1559     IN LPCSTR lpszFileExtension,
1560     OUT LPSTR lpszFileName,
1561     IN DWORD dwReserved
1562 )
1563 {
1564     URLCACHECONTAINER * pContainer;
1565     LPURLCACHE_HEADER pHeader;
1566     CHAR szFile[MAX_PATH];
1567     CHAR szExtension[MAX_PATH];
1568     LPCSTR lpszUrlPart;
1569     LPCSTR lpszUrlEnd;
1570     LPCSTR lpszFileNameExtension;
1571     LPSTR lpszFileNameNoPath;
1572     int i;
1573     int countnoextension;
1574     BYTE CacheDir;
1575     LONG lBufferSize;
1576     BOOL bFound = FALSE;
1577     int count;
1578
1579     TRACE("(%s, 0x%08lx, %s, %p, 0x%08lx)\n",
1580         debugstr_a(lpszUrlName),
1581         dwExpectedFileSize,
1582         debugstr_a(lpszFileExtension),
1583         lpszFileName,
1584         dwReserved);
1585
1586     if (dwReserved)
1587     {
1588         ERR("dwReserved != 0\n");
1589         SetLastError(ERROR_INVALID_PARAMETER);
1590         return FALSE;
1591     }
1592
1593     for (lpszUrlEnd = lpszUrlName; *lpszUrlEnd; lpszUrlEnd++)
1594         ;
1595     
1596     if (((lpszUrlEnd - lpszUrlName) > 1) && (*(lpszUrlEnd - 1) == '/'))
1597         lpszUrlEnd--;
1598
1599     for (lpszUrlPart = lpszUrlEnd; 
1600         (lpszUrlPart >= lpszUrlName); 
1601         lpszUrlPart--)
1602     {
1603         if ((*lpszUrlPart == '/') && ((lpszUrlEnd - lpszUrlPart) > 1))
1604         {
1605             bFound = TRUE;
1606             lpszUrlPart++;
1607             break;
1608         }
1609     }
1610     if (!strcmp(lpszUrlPart, "www"))
1611     {
1612         lpszUrlPart += strlen("www");
1613     }
1614
1615     count = lpszUrlEnd - lpszUrlPart;
1616
1617     if (bFound && (count < MAX_PATH))
1618     {
1619         memcpy(szFile, lpszUrlPart, count * sizeof(CHAR));
1620         szFile[count] = '\0';
1621         /* FIXME: get rid of illegal characters like \, / and : */
1622     }
1623     else
1624     {
1625         FIXME("need to generate a random filename\n");
1626     }
1627
1628     TRACE("File name: %s\n", szFile);
1629
1630     if (!URLCacheContainers_FindContainerA(lpszUrlName, &pContainer))
1631         return FALSE;
1632
1633     if (!URLCacheContainer_OpenIndex(pContainer))
1634         return FALSE;
1635
1636     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1637         return FALSE;
1638
1639     CacheDir = (BYTE)(rand() % pHeader->DirectoryCount);
1640
1641     lBufferSize = MAX_PATH * sizeof(CHAR);
1642     URLCache_LocalFileNameToPathA(pContainer, pHeader, szFile, CacheDir, lpszFileName, &lBufferSize);
1643
1644     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1645
1646     lpszFileNameNoPath = lpszFileName + lBufferSize / sizeof(CHAR) + DIR_LENGTH + 1;
1647
1648     countnoextension = strlen(lpszFileNameNoPath);
1649     lpszFileNameExtension = PathFindExtensionA(lpszFileNameNoPath);
1650     if (lpszFileNameExtension)
1651         countnoextension -= strlen(lpszFileNameExtension);
1652     *szExtension = '\0';
1653
1654     if (lpszFileExtension)
1655     {
1656         szExtension[0] = '.';
1657         strcpy(szExtension+1, lpszFileExtension);
1658     }
1659
1660     for (i = 0; i < 255; i++)
1661     {
1662         HANDLE hFile;
1663         strncpy(lpszFileNameNoPath, szFile, countnoextension);
1664         sprintf(lpszFileNameNoPath + countnoextension, "[%u]%s", i, szExtension);
1665         TRACE("Trying: %s\n", lpszFileName);
1666         hFile = CreateFileA(lpszFileName, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
1667         if (hFile != INVALID_HANDLE_VALUE)
1668         {
1669             CloseHandle(hFile);
1670             return TRUE;
1671         }
1672     }
1673
1674     return FALSE;
1675 }
1676
1677 /***********************************************************************
1678  *           CommitUrlCacheEntryA (WININET.@)
1679  *
1680  */
1681 BOOL WINAPI CommitUrlCacheEntryA(
1682     IN LPCSTR lpszUrlName,
1683     IN LPCSTR lpszLocalFileName,
1684     IN FILETIME ExpireTime,
1685     IN FILETIME LastModifiedTime,
1686     IN DWORD CacheEntryType,
1687     IN LPBYTE lpHeaderInfo,
1688     IN DWORD dwHeaderSize,
1689     IN LPCSTR lpszFileExtension,
1690     IN LPCSTR dwReserved
1691     )
1692 {
1693     URLCACHECONTAINER * pContainer;
1694     LPURLCACHE_HEADER pHeader;
1695     CACHEFILE_ENTRY * pEntry;
1696     URL_CACHEFILE_ENTRY * pUrlEntry;
1697     DWORD dwBytesNeeded = sizeof(*pUrlEntry) - sizeof(pUrlEntry->szSourceUrlName);
1698     DWORD dwOffsetLocalFileName = 0;
1699     DWORD dwOffsetHeader = 0;
1700     DWORD dwFileSizeLow = 0;
1701     DWORD dwFileSizeHigh = 0;
1702     BYTE cDirectory = 0;
1703
1704     TRACE("(%s, %s, ..., ..., %lx, %p, %ld, %s, %p)\n",
1705         debugstr_a(lpszUrlName),
1706         debugstr_a(lpszLocalFileName),
1707         CacheEntryType,
1708         lpHeaderInfo,
1709         dwHeaderSize,
1710         lpszFileExtension,
1711         dwReserved);
1712
1713     if (dwReserved)
1714     {
1715         ERR("dwReserved != 0\n");
1716         SetLastError(ERROR_INVALID_PARAMETER);
1717         return FALSE;
1718     }
1719     if (lpHeaderInfo == NULL)
1720     {
1721         FIXME("lpHeaderInfo == NULL - will crash at the moment\n");
1722     }
1723  
1724     if (lpszLocalFileName)
1725     {
1726         HANDLE hFile;
1727         hFile = CreateFileA(lpszLocalFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
1728         if (hFile == INVALID_HANDLE_VALUE)
1729         {
1730             ERR("couldn't open file %s (error is %ld)\n", debugstr_a(lpszLocalFileName), GetLastError());
1731             return FALSE;
1732         }
1733
1734         /* Get file size */
1735         dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
1736         if ((dwFileSizeLow == INVALID_FILE_SIZE) && (GetLastError() != NO_ERROR))
1737         {
1738             ERR("couldn't get file size (error is %ld)\n", GetLastError());
1739             CloseHandle(hFile);
1740             return FALSE;
1741         }
1742
1743         CloseHandle(hFile);
1744     }
1745
1746     if (!URLCacheContainers_FindContainerA(lpszUrlName, &pContainer))
1747         return FALSE;
1748
1749     if (!URLCacheContainer_OpenIndex(pContainer))
1750         return FALSE;
1751
1752     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
1753         return FALSE;
1754
1755     if (URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
1756     {
1757         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1758         FIXME("entry already in cache - don't know what to do!\n");
1759 /*
1760  *        SetLastError(ERROR_FILE_NOT_FOUND);
1761  *        return FALSE;
1762  */
1763         return TRUE;
1764     }
1765
1766     if (lpszLocalFileName)
1767     {
1768         BOOL bFound = FALSE;
1769         char szContainerPath[MAX_PATH];
1770         int container_path_len;
1771         container_path_len = WideCharToMultiByte(CP_ACP, 0, pContainer->path, -1, szContainerPath, sizeof(szContainerPath), NULL, NULL);
1772         if (!container_path_len)
1773         {
1774             /* WideCharToMultiByte should have called SetLastError */
1775             return FALSE;
1776         }
1777
1778         if (strncmp(lpszLocalFileName, szContainerPath, container_path_len))
1779         {
1780             URLCacheContainer_UnlockIndex(pContainer, pHeader);
1781             ERR("path %s must begin with cache content path %s\n", debugstr_a(lpszLocalFileName), debugstr_a(szContainerPath));
1782             SetLastError(ERROR_INVALID_PARAMETER);
1783             return FALSE;
1784         }
1785
1786         /* skip container path prefix */
1787         lpszLocalFileName += container_path_len;
1788
1789         for (cDirectory = 0; cDirectory < pHeader->DirectoryCount; cDirectory++)
1790         {
1791             if (!strncmp(pHeader->directory_data[cDirectory].filename, lpszLocalFileName, DIR_LENGTH))
1792             {
1793                 bFound = TRUE;
1794                 break;
1795             }
1796         }
1797
1798         if (!bFound)
1799         {
1800             URLCacheContainer_UnlockIndex(pContainer, pHeader);
1801             ERR("cache directory not found in path %s\n", lpszLocalFileName);
1802             SetLastError(ERROR_INVALID_PARAMETER);
1803             return FALSE;
1804         }
1805
1806         lpszLocalFileName += (DIR_LENGTH + 1); /* "1234WXYZ\" */
1807     }
1808
1809     dwBytesNeeded = DWORD_ALIGN(dwBytesNeeded + strlen(lpszUrlName) + 1);
1810     if (lpszLocalFileName)
1811     {
1812         dwOffsetLocalFileName = dwBytesNeeded;
1813         dwBytesNeeded = DWORD_ALIGN(dwBytesNeeded + strlen(lpszLocalFileName) + 1);
1814     }
1815     if (lpHeaderInfo)
1816     {
1817         dwOffsetHeader = dwBytesNeeded;
1818         dwBytesNeeded = DWORD_ALIGN(dwBytesNeeded + dwHeaderSize);
1819     }
1820
1821     /* round up to next block */
1822     if (dwBytesNeeded % BLOCKSIZE)
1823     {
1824         dwBytesNeeded -= dwBytesNeeded % BLOCKSIZE;
1825         dwBytesNeeded += BLOCKSIZE;
1826     }
1827
1828     if (!URLCache_FindFirstFreeEntry(pHeader, dwBytesNeeded / BLOCKSIZE, &pEntry))
1829     {
1830         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1831         ERR("no free entries\n");
1832         return FALSE;
1833     }
1834
1835     /* FindFirstFreeEntry fills in blocks used */
1836     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
1837     pUrlEntry->CacheFileEntry.dwSignature = URL_SIGNATURE;
1838     pUrlEntry->CacheDir = cDirectory;
1839     pUrlEntry->CacheEntryType = CacheEntryType;
1840     pUrlEntry->dwHeaderInfoSize = dwHeaderSize;
1841     pUrlEntry->dwExemptDelta = 0;
1842     pUrlEntry->dwHitRate = 0;
1843     pUrlEntry->dwOffsetHeaderInfo = dwOffsetHeader;
1844     pUrlEntry->dwOffsetLocalName = dwOffsetLocalFileName;
1845     pUrlEntry->dwOffsetUrl = sizeof(*pUrlEntry) - sizeof(pUrlEntry->szSourceUrlName);
1846     pUrlEntry->dwSizeHigh = 0;
1847     pUrlEntry->dwSizeLow = dwFileSizeLow;
1848     pUrlEntry->dwSizeHigh = dwFileSizeHigh;
1849     pUrlEntry->dwUseCount = 0;
1850     GetSystemTimeAsFileTime(&pUrlEntry->LastAccessTime);
1851     pUrlEntry->LastModifiedTime = LastModifiedTime;
1852     FileTimeToDosDateTime(&pUrlEntry->LastAccessTime, &pUrlEntry->wLastSyncDate, &pUrlEntry->wLastSyncTime);
1853     FileTimeToDosDateTime(&ExpireTime, &pUrlEntry->wExpiredDate, &pUrlEntry->wExpiredTime);
1854     pUrlEntry->wUnknownDate = pUrlEntry->wLastSyncDate;
1855     pUrlEntry->wUnknownTime = pUrlEntry->wLastSyncTime;
1856
1857     /*** Unknowns ***/
1858     pUrlEntry->dwUnknown1 = 0;
1859     pUrlEntry->dwUnknown2 = 0;
1860     pUrlEntry->dwUnknown3 = 0x60;
1861     pUrlEntry->Unknown4 = 0;
1862     pUrlEntry->wUnknown5 = 0x1010;
1863     pUrlEntry->dwUnknown6 = 0;
1864     pUrlEntry->dwUnknown7 = 0;
1865     pUrlEntry->dwUnknown8 = 0;
1866
1867     strcpy(pUrlEntry->szSourceUrlName, lpszUrlName);
1868     if (dwOffsetLocalFileName)
1869         strcpy((LPSTR)((LPBYTE)pUrlEntry + dwOffsetLocalFileName), lpszLocalFileName);
1870     if (dwOffsetHeader)
1871         memcpy((LPSTR)((LPBYTE)pUrlEntry + dwOffsetHeader), lpHeaderInfo, dwHeaderSize);
1872
1873     if (!URLCache_AddEntryToHash(pHeader, lpszUrlName, (DWORD)((LPBYTE)pUrlEntry - (LPBYTE)pHeader)))
1874     {
1875         URLCacheContainer_UnlockIndex(pContainer, pHeader);
1876         return FALSE;
1877     }
1878
1879     URLCacheContainer_UnlockIndex(pContainer, pHeader);
1880
1881     return TRUE;
1882 }
1883
1884 BOOL WINAPI ReadUrlCacheEntryStream(
1885     IN HANDLE hUrlCacheStream,
1886     IN  DWORD dwLocation,
1887     IN OUT LPVOID lpBuffer,
1888     IN OUT LPDWORD lpdwLen,
1889     IN DWORD dwReserved
1890     )
1891 {
1892     /* Get handle to file from 'stream' */
1893     STREAM_HANDLE * pStream = (STREAM_HANDLE *)hUrlCacheStream;
1894
1895     if (dwReserved != 0)
1896     {
1897         ERR("dwReserved != 0\n");
1898         SetLastError(ERROR_INVALID_PARAMETER);
1899         return FALSE;
1900     }
1901
1902     if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->lpszUrl, INTERNET_MAX_URL_LENGTH))
1903     {
1904         SetLastError(ERROR_INVALID_HANDLE);
1905         return FALSE;
1906     }
1907
1908     if (SetFilePointer(pStream->hFile, dwLocation, NULL, FILE_CURRENT) == INVALID_SET_FILE_POINTER)
1909         return FALSE;
1910     return ReadFile(pStream->hFile, lpBuffer, *lpdwLen, lpdwLen, NULL);
1911 }
1912
1913 /***********************************************************************
1914  *           RetrieveUrlCacheEntryStreamA (WININET.@)
1915  *
1916  */
1917 HANDLE WINAPI RetrieveUrlCacheEntryStreamA(
1918     IN LPCSTR lpszUrlName,
1919     OUT LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1920     IN OUT LPDWORD lpdwCacheEntryInfoBufferSize,
1921     IN BOOL fRandomRead,
1922     IN DWORD dwReserved
1923     )
1924 {
1925     /* NOTE: this is not the same as the way that the native
1926      * version allocates 'stream' handles. I did it this way
1927      * as it is much easier and no applications should depend
1928      * on this behaviour. (Native version appears to allocate
1929      * indices into a table)
1930      */
1931     STREAM_HANDLE * pStream;
1932     HANDLE hFile;
1933
1934     if (!RetrieveUrlCacheEntryFileA(lpszUrlName, 
1935         lpCacheEntryInfo, 
1936         lpdwCacheEntryInfoBufferSize,
1937         dwReserved))
1938     {
1939         return NULL;
1940     }
1941     
1942     hFile = CreateFileA(lpCacheEntryInfo->lpszLocalFileName,
1943         GENERIC_READ,
1944         FILE_SHARE_READ,
1945         NULL,
1946         OPEN_EXISTING,
1947         fRandomRead ? FILE_FLAG_RANDOM_ACCESS : 0,
1948         NULL);
1949     if (hFile == INVALID_HANDLE_VALUE)
1950         return FALSE;
1951     
1952     /* allocate handle storage space */
1953     pStream = (STREAM_HANDLE *)HeapAlloc(GetProcessHeap(), 0, sizeof(STREAM_HANDLE) + strlen(lpszUrlName) * sizeof(CHAR));
1954     if (!pStream)
1955     {
1956         CloseHandle(hFile);
1957         SetLastError(ERROR_OUTOFMEMORY);
1958         return FALSE;
1959     }
1960
1961     pStream->hFile = hFile;
1962     strcpy(pStream->lpszUrl, lpszUrlName);
1963     return (HANDLE)pStream;
1964 }
1965
1966 /***********************************************************************
1967  *           UnlockUrlCacheEntryStream (WININET.@)
1968  *
1969  */
1970 BOOL WINAPI UnlockUrlCacheEntryStream(
1971     IN HANDLE hUrlCacheStream,
1972     IN DWORD dwReserved
1973 )
1974 {
1975     STREAM_HANDLE * pStream = (STREAM_HANDLE *)hUrlCacheStream;
1976
1977     if (dwReserved != 0)
1978     {
1979         ERR("dwReserved != 0\n");
1980         SetLastError(ERROR_INVALID_PARAMETER);
1981         return FALSE;
1982     }
1983
1984     if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->lpszUrl, INTERNET_MAX_URL_LENGTH))
1985     {
1986         SetLastError(ERROR_INVALID_HANDLE);
1987         return FALSE;
1988     }
1989
1990     if (!UnlockUrlCacheEntryFileA(pStream->lpszUrl, 0))
1991         return FALSE;
1992
1993     /* close file handle */
1994     CloseHandle(pStream->hFile);
1995
1996     /* free allocated space */
1997     HeapFree(GetProcessHeap(), 0, pStream);
1998
1999     return TRUE;
2000 }
2001
2002
2003 /***********************************************************************
2004  *           DeleteUrlCacheEntryA (WININET.@)
2005  *
2006  */
2007 BOOL WINAPI DeleteUrlCacheEntryA(LPCSTR lpszUrlName)
2008 {
2009     URLCACHECONTAINER * pContainer;
2010     LPURLCACHE_HEADER pHeader;
2011     CACHEFILE_ENTRY * pEntry;
2012     DWORD dwStartBlock;
2013     DWORD dwBlock;
2014     BYTE * AllocationTable;
2015
2016     TRACE("(%s)\n", debugstr_a(lpszUrlName));
2017
2018     if (!URLCacheContainers_FindContainerA(lpszUrlName, &pContainer))
2019         return FALSE;
2020
2021     if (!URLCacheContainer_OpenIndex(pContainer))
2022         return FALSE;
2023
2024     if (!(pHeader = URLCacheContainer_LockIndex(pContainer)))
2025         return FALSE;
2026
2027     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
2028     {
2029         URLCacheContainer_UnlockIndex(pContainer, pHeader);
2030         TRACE("entry %s not found!\n", lpszUrlName);
2031         SetLastError(ERROR_FILE_NOT_FOUND);
2032         return FALSE;
2033     }
2034
2035     AllocationTable = (LPBYTE)pHeader + ALLOCATION_TABLE_OFFSET;
2036
2037     /* update allocation table */
2038     dwStartBlock = ((DWORD)pEntry - (DWORD)pHeader) / BLOCKSIZE;
2039     for (dwBlock = dwStartBlock; dwBlock < dwStartBlock + pEntry->dwBlocksUsed; dwBlock++)
2040         URLCache_Allocation_BlockFree(AllocationTable, dwBlock);
2041
2042     URLCache_DeleteEntry(pEntry);
2043
2044     URLCache_DeleteEntryFromHash(pHeader, lpszUrlName);
2045
2046     URLCacheContainer_UnlockIndex(pContainer, pHeader);
2047
2048     return TRUE;
2049 }
2050
2051 /***********************************************************************
2052  *           FindFirstUrlCacheEntryA (WININET.@)
2053  *
2054  */
2055 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryA(LPCSTR lpszUrlSearchPattern,
2056  LPINTERNET_CACHE_ENTRY_INFOA lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
2057 {
2058   FIXME("(%s, %p, %p): stub\n", debugstr_a(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
2059   SetLastError(ERROR_FILE_NOT_FOUND);
2060   return 0;
2061 }
2062
2063 /***********************************************************************
2064  *           FindFirstUrlCacheEntryW (WININET.@)
2065  *
2066  */
2067 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryW(LPCWSTR lpszUrlSearchPattern,
2068  LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
2069 {
2070   FIXME("(%s, %p, %p): stub\n", debugstr_w(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
2071   return 0;
2072 }
2073
2074 /***********************************************************************
2075  *           CreateUrlCacheGroup (WININET.@)
2076  *
2077  */
2078 INTERNETAPI GROUPID WINAPI CreateUrlCacheGroup(DWORD dwFlags, LPVOID 
2079 lpReserved)
2080 {
2081   FIXME("(%lx, %p): stub\n", dwFlags, lpReserved);
2082   return FALSE;
2083 }
2084
2085 /***********************************************************************
2086  *           DeleteUrlCacheGroup (WININET.@)
2087  *
2088  */
2089 BOOL WINAPI DeleteUrlCacheGroup(GROUPID GroupId, DWORD dwFlags, LPVOID lpReserved)
2090 {
2091     FIXME("STUB\n");
2092     return FALSE;
2093 }
2094
2095 /***********************************************************************
2096  *           SetUrlCacheEntryGroup (WININET.@)
2097  *
2098  */
2099 BOOL WINAPI SetUrlCacheEntryGroup(LPCSTR lpszUrlName, DWORD dwFlags,
2100   GROUPID GroupId, LPBYTE pbGroupAttributes, DWORD cbGroupAttributes,
2101   LPVOID lpReserved)
2102 {
2103     FIXME("STUB\n");
2104     SetLastError(ERROR_FILE_NOT_FOUND);
2105     return FALSE;
2106 }
2107
2108 /***********************************************************************
2109  *           GetUrlCacheConfigInfoW (WININET.@)
2110  */
2111 BOOL WINAPI GetUrlCacheConfigInfoW(LPDWORD CacheInfo, LPDWORD size, DWORD bitmask)
2112 {
2113     FIXME("(%p, %p, %lx)\n", CacheInfo, size, bitmask);
2114     INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
2115     return FALSE;
2116 }
2117
2118 /***********************************************************************
2119  *           GetUrlCacheConfigInfoA (WININET.@)
2120  *
2121  * CacheInfo is some CACHE_CONFIG_INFO structure, with no MS info found by google
2122  */
2123 BOOL WINAPI GetUrlCacheConfigInfoA(LPDWORD CacheInfo, LPDWORD size, DWORD bitmask)
2124 {
2125     FIXME("(%p, %p, %lx)\n", CacheInfo, size, bitmask);
2126     INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
2127     return FALSE;
2128 }
2129
2130 /***********************************************************************
2131  *           DeleteIE3Cache (WININET.@)
2132  *
2133  * Deletes the files used by the IE3 URL caching system.
2134  *
2135  * PARAMS
2136  *   hWnd        [I] A dummy window.
2137  *   hInst       [I] Instance of process calling the function.
2138  *   lpszCmdLine [I] Options used by function.
2139  *   nCmdShow    [I] The nCmdShow value to use when showing windows created, if any.
2140  *
2141  * RETURNS
2142  *   nothing
2143  */
2144 void WINAPI DeleteIE3Cache(HWND hWnd, HINSTANCE hInst, LPSTR lpszCmdLine, int nCmdShow)
2145 {
2146     FIXME("(%p, %p, %s, %d)\n", hWnd, hInst, debugstr_a(lpszCmdLine), nCmdShow);
2147 }