- Documentation of the index.dat files.
[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
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32
33 #include "windef.h"
34 #include "winbase.h"
35 #include "winuser.h"
36 #include "wininet.h"
37 #include "winerror.h"
38 #include "internet.h"
39 #include "winreg.h"
40 #include "shlwapi.h"
41 #include "shlobj.h"
42
43 #include "wine/debug.h"
44
45 WINE_DEFAULT_DEBUG_CHANNEL(wininet);
46
47 #define ENTRY_START_OFFSET  0x4000
48 #define DIR_LENGTH          8
49 #define BLOCKSIZE           128
50 #define CONTENT_DIRECTORY   "\\Content.IE5\\"
51 #define HASHTABLE_SIZE      448
52 #define HASHTABLE_BLOCKSIZE 7
53 #define ALLOCATION_TABLE_OFFSET 0x250
54 #define ALLOCATION_TABLE_SIZE   (0x1000 - ALLOCATION_TABLE_OFFSET)
55 #define HASHTABLE_NUM_ENTRIES   (HASHTABLE_SIZE / HASHTABLE_BLOCKSIZE)
56
57 #define DWORD_SIG(a,b,c,d)  (a | (b << 8) | (c << 16) | (d << 24))
58 #define URL_SIGNATURE   DWORD_SIG('U','R','L',' ')
59 #define REDR_SIGNATURE  DWORD_SIG('R','E','D','R')
60 #define LEAK_SIGNATURE  DWORD_SIG('L','E','A','K')
61 #define HASH_SIGNATURE  DWORD_SIG('H','A','S','H')
62
63 #define DWORD_ALIGN(x) ( (DWORD)(((DWORD)(x) + 3) >> 2) << 2)
64
65 typedef struct _CACHEFILE_ENTRY
66 {
67 /*  union
68     {*/
69         DWORD dwSignature; /* e.g. "URL " */
70 /*      CHAR szSignature[4];
71     };*/
72     DWORD dwBlocksUsed; /* number of 128byte blocks used by this entry */
73 } CACHEFILE_ENTRY;
74
75 typedef struct _URL_CACHEFILE_ENTRY
76 {
77     CACHEFILE_ENTRY CacheFileEntry;
78     FILETIME LastModifiedTime;
79     FILETIME LastAccessTime;
80     WORD wExpiredDate; /* expire date in dos format */
81     WORD wExpiredTime; /* expire time in dos format */
82     DWORD dwUnknown1; /* usually zero */
83     DWORD dwSizeLow; /* see INTERNET_CACHE_ENTRY_INFO::dwSizeLow */
84     DWORD dwSizeHigh; /* see INTERNET_CACHE_ENTRY_INFO::dwSizeHigh */
85     DWORD dwUnknown2; /* usually zero */
86     DWORD dwExemptDelta; /* see INTERNET_CACHE_ENTRY_INFO::dwExemptDelta */
87     DWORD dwUnknown3; /* usually 0x60 */
88     DWORD dwOffsetUrl; /* usually 0x68 */
89     BYTE CacheDir; /* index of cache directory this url is stored in */
90     BYTE Unknown4; /* usually zero */
91     WORD wUnknown5; /* usually 0x1010 */
92     DWORD dwOffsetLocalName; /* offset of start of local filename from start of entry */
93     DWORD CacheEntryType; /* see INTERNET_CACHE_ENTRY_INFO::CacheEntryType */
94     DWORD dwOffsetHeaderInfo; /* offset of start of header info from start of entry */
95     DWORD dwHeaderInfoSize;
96     DWORD dwUnknown6; /* usually zero */
97     WORD wLastSyncDate; /* last sync date in dos format */
98     WORD wLastSyncTime; /* last sync time in dos format */
99     DWORD dwHitRate; /* see INTERNET_CACHE_ENTRY_INFO::dwHitRate */
100     DWORD dwUseCount; /* see INTERNET_CACHE_ENTRY_INFO::dwUseCount */
101     WORD wUnknownDate; /* usually same as wLastSyncDate */
102     WORD wUnknownTime; /* usually same as wLastSyncTime */
103     DWORD dwUnknown7; /* usually zero */
104     DWORD dwUnknown8; /* usually zero */
105     CHAR szSourceUrlName[1]; /* start of url */
106     /* packing to dword align start of next field */
107     /* CHAR szLocalFileName[]; (local file name exluding path) */
108     /* packing to dword align start of next field */
109     /* CHAR szHeaderInfo[]; (header info) */
110 } URL_CACHEFILE_ENTRY;
111
112 struct _HASH_ENTRY
113 {
114     DWORD dwHashKey;
115     DWORD dwOffsetEntry;
116 };
117
118 typedef struct _HASH_CACHEFILE_ENTRY
119 {
120     CACHEFILE_ENTRY CacheFileEntry;
121     DWORD dwAddressNext;
122     DWORD dwHashTableNumber;
123     struct _HASH_ENTRY HashTable[HASHTABLE_SIZE];
124 } HASH_CACHEFILE_ENTRY;
125
126 typedef struct _DIRECTORY_DATA
127 {
128     DWORD dwUnknown;
129     char filename[DIR_LENGTH];
130 } DIRECTORY_DATA;
131
132 typedef struct _URLCACHE_HEADER
133 {
134     char szSignature[28];
135     DWORD dwFileSize;
136     DWORD dwOffsetFirstHashTable;
137     DWORD dwIndexCapacityInBlocks;
138     DWORD dwBlocksInUse; /* is this right? */
139     DWORD dwUnknown1;
140     DWORD dwCacheLimitLow; /* disk space limit for cache */
141     DWORD dwCacheLimitHigh; /* disk space limit for cache */
142     DWORD dwUnknown4; /* current disk space usage for cache? */
143     DWORD dwUnknown5; /* current disk space usage for cache? */
144     DWORD dwUnknown6; /* possibly a flag? */
145     DWORD dwUnknown7;
146     BYTE DirectoryCount; /* number of directory_data's */
147     BYTE Unknown8[3]; /* just padding? */
148     DIRECTORY_DATA directory_data[1]; /* first directory entry */
149 } URLCACHE_HEADER, *LPURLCACHE_HEADER;
150 typedef const URLCACHE_HEADER *LPCURLCACHE_HEADER;
151
152
153 typedef struct _STREAM_HANDLE
154 {
155     HANDLE hFile;
156     CHAR lpszUrl[1];
157 } STREAM_HANDLE;
158
159 /**** File Global Variables ****/
160 static HANDLE hCacheIndexMapping = NULL; /* handle to file mapping */
161 static LPSTR szCacheContentPath = NULL; /* path to content index */
162 static HANDLE hMutex = NULL;
163
164 /***********************************************************************
165  *           URLCache_PathToObjectName (Internal)
166  *
167  *  Converts a path to a name suitable for use as a Win32 object name.
168  * Replaces '\\' characters in-place with the specified character
169  * (usually '_' or '!')
170  *
171  * RETURNS
172  *    nothing
173  *
174  */
175 static void URLCache_PathToObjectName(LPSTR lpszPath, char replace)
176 {
177     char ch;
178     for (ch = *lpszPath; (ch = *lpszPath); lpszPath++)
179     {
180         if (ch == '\\')
181             *lpszPath = replace;
182     }
183 }
184
185 #ifndef CHAR_BIT
186 #define CHAR_BIT    (8 * sizeof(CHAR))
187 #endif
188
189 /***********************************************************************
190  *           URLCache_Allocation_BlockIsFree (Internal)
191  *
192  *  Is the specified block number free?
193  *
194  * RETURNS
195  *    zero if free
196  *    non-zero otherwise
197  *
198  */
199 static inline BYTE URLCache_Allocation_BlockIsFree(BYTE * AllocationTable, DWORD dwBlockNumber)
200 {
201     BYTE mask = 1 << (dwBlockNumber % CHAR_BIT);
202     return (AllocationTable[dwBlockNumber / CHAR_BIT] & mask) == 0;
203 }
204
205 /***********************************************************************
206  *           URLCache_Allocation_BlockFree (Internal)
207  *
208  *  Marks the specified block as free
209  *
210  * RETURNS
211  *    nothing
212  *
213  */
214 static inline void URLCache_Allocation_BlockFree(BYTE * AllocationTable, DWORD dwBlockNumber)
215 {
216     BYTE mask = ~(1 << (dwBlockNumber % CHAR_BIT));
217     AllocationTable[dwBlockNumber / CHAR_BIT] &= mask;
218 }
219
220 /***********************************************************************
221  *           URLCache_Allocation_BlockAllocate (Internal)
222  *
223  *  Marks the specified block as allocated
224  *
225  * RETURNS
226  *    nothing
227  *
228  */
229 static inline void URLCache_Allocation_BlockAllocate(BYTE * AllocationTable, DWORD dwBlockNumber)
230 {
231     BYTE mask = 1 << (dwBlockNumber % CHAR_BIT);
232     AllocationTable[dwBlockNumber / CHAR_BIT] |= mask;
233 }
234
235 /***********************************************************************
236  *           URLCache_FindEntry (Internal)
237  *
238  *  Finds an entry without using the hash tables
239  *
240  * RETURNS
241  *    TRUE if it found the specified entry
242  *    FALSE otherwise
243  *
244  */
245 static BOOL URLCache_FindEntry(LPCURLCACHE_HEADER pHeader, LPCSTR szUrl, CACHEFILE_ENTRY ** ppEntry)
246 {
247     CACHEFILE_ENTRY * pCurrentEntry;
248     DWORD dwBlockNumber;
249
250     BYTE * AllocationTable = (LPBYTE)pHeader + ALLOCATION_TABLE_OFFSET;
251
252     for (pCurrentEntry = (CACHEFILE_ENTRY *)((LPBYTE)pHeader + ENTRY_START_OFFSET);
253          (DWORD)((LPBYTE)pCurrentEntry - (LPBYTE)pHeader) < pHeader->dwFileSize;
254          pCurrentEntry = (CACHEFILE_ENTRY *)((LPBYTE)pCurrentEntry + pCurrentEntry->dwBlocksUsed * BLOCKSIZE))
255     {
256         dwBlockNumber = (DWORD)((LPBYTE)pCurrentEntry - (LPBYTE)pHeader - ENTRY_START_OFFSET) / BLOCKSIZE;
257         while (URLCache_Allocation_BlockIsFree(AllocationTable, dwBlockNumber))
258         {
259             if (dwBlockNumber >= pHeader->dwIndexCapacityInBlocks)
260                 return FALSE;
261
262             pCurrentEntry = (CACHEFILE_ENTRY *)((LPBYTE)pCurrentEntry + BLOCKSIZE);
263             dwBlockNumber = (DWORD)((LPBYTE)pCurrentEntry - (LPBYTE)pHeader - ENTRY_START_OFFSET) / BLOCKSIZE;
264         }
265
266         switch (pCurrentEntry->dwSignature)
267         {
268         case URL_SIGNATURE: /* "URL " */
269         case LEAK_SIGNATURE: /* "LEAK" */
270             {
271                 URL_CACHEFILE_ENTRY * pUrlEntry = (URL_CACHEFILE_ENTRY *)pCurrentEntry;
272                 if (!strcmp(szUrl, pUrlEntry->szSourceUrlName))
273                 {
274                     *ppEntry = pCurrentEntry;
275                     /* FIXME: should we update the LastAccessTime here? */
276                     return TRUE;
277                 }
278             }
279             break;
280         case HASH_SIGNATURE: /* HASH entries parsed in FindEntryInHash */
281         case 0xDEADBEEF: /* this is always at offset 0x4000 in URL cache for some reason */
282             break;
283         default:
284             FIXME("Unknown entry %.4s ignored\n", (LPCSTR)&pCurrentEntry->dwSignature);
285         }
286
287     }
288     return FALSE;
289 }
290
291 /***********************************************************************
292  *           URLCache_OpenIndex (Internal)
293  *
294  *  Opens the index file and saves mapping handle in hCacheIndexMapping
295  *
296  * RETURNS
297  *    TRUE if succeeded
298  *    FALSE if failed
299  *
300  */
301 static BOOL URLCache_OpenIndex()
302 {
303     HANDLE hFile;
304     CHAR szFullPath[MAX_PATH];
305     CHAR szFileMappingName[MAX_PATH+10];
306     CHAR szMutexName[MAX_PATH+1];
307     DWORD dwFileSize;
308     
309     if (!szCacheContentPath)
310     {
311         szCacheContentPath = (LPSTR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(CHAR));
312         *szCacheContentPath = '\0';
313     }
314
315     if (*szCacheContentPath == '\0')
316     {
317         if (FAILED(SHGetSpecialFolderPathA(NULL, szCacheContentPath, CSIDL_INTERNET_CACHE, TRUE)))
318             return FALSE;
319         strcat(szCacheContentPath, CONTENT_DIRECTORY);
320     }
321
322     strcpy(szFullPath, szCacheContentPath);
323     strcat(szFullPath, "index.dat");
324
325     if (hCacheIndexMapping)
326         return TRUE;
327
328     hFile = CreateFileA(szFullPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
329     if (hFile == INVALID_HANDLE_VALUE)
330     {
331         FIXME("need to create cache index file\n");
332         return FALSE;
333     }
334
335     dwFileSize = GetFileSize(hFile, NULL);
336     if (dwFileSize == INVALID_FILE_SIZE)
337         return FALSE;
338
339     if (dwFileSize == 0)
340     {
341         FIXME("need to create cache index file\n");
342         return FALSE;
343     }
344
345     strcpy(szFileMappingName, szFullPath);
346     sprintf(szFileMappingName + strlen(szFileMappingName), "\\%lu", dwFileSize);
347     URLCache_PathToObjectName(szFileMappingName, '_');
348     hCacheIndexMapping = OpenFileMappingA(FILE_MAP_WRITE, FALSE, szFileMappingName);
349     if (!hCacheIndexMapping)
350         hCacheIndexMapping = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, 0, szFileMappingName);
351     CloseHandle(hFile);
352     if (!hCacheIndexMapping)
353     {
354         ERR("Couldn't create file mapping (error is %ld)\n", GetLastError());
355         return FALSE;
356     }
357
358     strcpy(szMutexName, szFullPath);
359     CharLowerA(szMutexName);
360     URLCache_PathToObjectName(szMutexName, '!');
361     strcat(szMutexName, "!");
362
363     if ((hMutex = CreateMutexA(NULL, FALSE, szMutexName)) == NULL)
364     {
365         ERR("couldn't create mutex (error is %ld)\n", GetLastError());
366         CloseHandle(hCacheIndexMapping);
367         return FALSE;
368     }
369
370     return TRUE;
371 }
372
373 /***********************************************************************
374  *           URLCache_CloseIndex (Internal)
375  *
376  *  Closes the index
377  *
378  * RETURNS
379  *    nothing
380  *
381  */
382 #if 0 /* not used at the moment */
383 static BOOL URLCache_CloseIndex()
384 {
385     return CloseHandle(hCacheIndexMapping);
386 }
387 #endif
388
389 /***********************************************************************
390  *           URLCache_FindFirstFreeEntry (Internal)
391  *
392  *  Finds and allocates the first block of free space big enough and
393  * sets ppEntry to point to it.
394  *
395  * RETURNS
396  *    TRUE if it had enough space
397  *    FALSE if it couldn't find enough space
398  *
399  */
400 static BOOL URLCache_FindFirstFreeEntry(URLCACHE_HEADER * pHeader, DWORD dwBlocksNeeded, CACHEFILE_ENTRY ** ppEntry)
401 {
402     LPBYTE AllocationTable = (LPBYTE)pHeader + ALLOCATION_TABLE_OFFSET;
403     DWORD dwBlockNumber;
404     DWORD dwFreeCounter;
405     for (dwBlockNumber = 0; dwBlockNumber < pHeader->dwIndexCapacityInBlocks; dwBlockNumber++)
406     {
407         for (dwFreeCounter = 0; 
408             dwFreeCounter < dwBlocksNeeded &&
409               dwFreeCounter + dwBlockNumber < pHeader->dwIndexCapacityInBlocks &&
410               URLCache_Allocation_BlockIsFree(AllocationTable, dwBlockNumber + dwFreeCounter);
411             dwFreeCounter++)
412                 TRACE("Found free block at no. %ld (0x%lx)\n", dwBlockNumber, ENTRY_START_OFFSET + dwBlockNumber * BLOCKSIZE);
413
414         if (dwFreeCounter == dwBlocksNeeded)
415         {
416             DWORD index;
417             TRACE("Found free blocks starting at no. %ld (0x%lx)\n", dwBlockNumber, ENTRY_START_OFFSET + dwBlockNumber * BLOCKSIZE);
418             for (index = 0; index < dwBlocksNeeded; index++)
419                 URLCache_Allocation_BlockAllocate(AllocationTable, dwBlockNumber + index);
420             *ppEntry = (CACHEFILE_ENTRY *)((LPBYTE)pHeader + ENTRY_START_OFFSET + dwBlockNumber * BLOCKSIZE);
421             (*ppEntry)->dwBlocksUsed = dwBlocksNeeded;
422             return TRUE;
423         }
424     }
425     return FALSE;
426 }
427
428 /***********************************************************************
429  *           URLCache_DeleteEntry (Internal)
430  *
431  *  Deletes the specified entry and frees the space allocated to it
432  *
433  * RETURNS
434  *    TRUE if it succeeded
435  *    FALSE if it failed
436  *
437  */
438 static BOOL URLCache_DeleteEntry(CACHEFILE_ENTRY * pEntry)
439 {
440     ZeroMemory(pEntry, pEntry->dwBlocksUsed * BLOCKSIZE);
441     return TRUE;
442 }
443
444 /***********************************************************************
445  *           URLCache_LockIndex (Internal)
446  *
447  */
448 static LPURLCACHE_HEADER URLCache_LockIndex()
449 {
450     BYTE index;
451     LPVOID pIndexData = MapViewOfFile(hCacheIndexMapping, FILE_MAP_WRITE, 0, 0, 0);
452     URLCACHE_HEADER * pHeader = (URLCACHE_HEADER *)pIndexData;
453     if (!pIndexData)
454         return FALSE;
455
456     TRACE("Signature: %s, file size: %ld bytes\n", pHeader->szSignature, pHeader->dwFileSize);
457
458     for (index = 0; index < pHeader->DirectoryCount; index++)
459     {
460         TRACE("Directory[%d] = \"%.8s\"\n", index, pHeader->directory_data[index].filename);
461     }
462     
463     /* acquire mutex */
464     WaitForSingleObject(hMutex, INFINITE);
465
466     return pHeader;
467 }
468
469 /***********************************************************************
470  *           URLCache_UnlockIndex (Internal)
471  *
472  */
473 static BOOL URLCache_UnlockIndex(LPURLCACHE_HEADER pHeader)
474 {
475     /* release mutex */
476     ReleaseMutex(hMutex);
477     return UnmapViewOfFile(pHeader);
478 }
479
480 /***********************************************************************
481  *           URLCache_LocalFileNameToPath (Internal)
482  *
483  *  Copies the full path to the specified buffer given the local file
484  * name and the index of the directory it is in. Always sets value in
485  * lpBufferSize to the required buffer size.
486  *
487  * RETURNS
488  *    TRUE if the buffer was big enough
489  *    FALSE if the buffer was too small
490  *
491  */
492 static BOOL URLCache_LocalFileNameToPath(LPCURLCACHE_HEADER pHeader, LPCSTR szLocalFileName, BYTE Directory, LPSTR szPath, LPLONG lpBufferSize)
493 {
494     LONG nRequired;
495     if (Directory >= pHeader->DirectoryCount)
496     {
497         *lpBufferSize = 0;
498         return FALSE;
499     }
500
501     nRequired = (strlen(szCacheContentPath) + DIR_LENGTH + strlen(szLocalFileName) + 1) * sizeof(CHAR);
502     if (nRequired < *lpBufferSize)
503     {
504         strcpy(szPath, szCacheContentPath);
505         strncat(szPath, pHeader->directory_data[Directory].filename, DIR_LENGTH);
506         strcat(szPath, "\\");
507         strcat(szPath, szLocalFileName);
508         *lpBufferSize = nRequired;
509         return TRUE;
510     }
511     *lpBufferSize = nRequired;
512     return FALSE;
513 }
514
515 /***********************************************************************
516  *           URLCache_CopyEntry (Internal)
517  *
518  *  Copies an entry from the cache index file to the Win32 structure
519  *
520  * RETURNS
521  *    TRUE if the buffer was big enough
522  *    FALSE if the buffer was too small
523  *
524  */
525 static BOOL URLCache_CopyEntry(LPCURLCACHE_HEADER pHeader, LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo, LPDWORD lpdwBufferSize, URL_CACHEFILE_ENTRY * pUrlEntry)
526 {
527     int lenUrl = strlen(pUrlEntry->szSourceUrlName);
528     DWORD dwRequiredSize = sizeof(*lpCacheEntryInfo);
529     LONG nLocalFilePathSize;
530     LPSTR lpszLocalFileName;
531
532     if (*lpdwBufferSize >= dwRequiredSize)
533     {
534         lpCacheEntryInfo->lpHeaderInfo = NULL;
535         lpCacheEntryInfo->lpszFileExtension = NULL;
536         lpCacheEntryInfo->lpszLocalFileName = NULL;
537         lpCacheEntryInfo->lpszSourceUrlName = NULL;
538         lpCacheEntryInfo->CacheEntryType = pUrlEntry->CacheEntryType;
539         lpCacheEntryInfo->u.dwExemptDelta = pUrlEntry->dwExemptDelta;
540         lpCacheEntryInfo->dwHeaderInfoSize = pUrlEntry->dwHeaderInfoSize;
541         lpCacheEntryInfo->dwHitRate = pUrlEntry->dwHitRate;
542         lpCacheEntryInfo->dwSizeHigh = pUrlEntry->dwSizeHigh;
543         lpCacheEntryInfo->dwSizeLow = pUrlEntry->dwSizeLow;
544         lpCacheEntryInfo->dwStructSize = sizeof(*lpCacheEntryInfo);
545         lpCacheEntryInfo->dwUseCount = pUrlEntry->dwUseCount;
546         DosDateTimeToFileTime(pUrlEntry->wExpiredDate, pUrlEntry->wExpiredTime, &lpCacheEntryInfo->ExpireTime);
547         lpCacheEntryInfo->LastAccessTime.dwHighDateTime = pUrlEntry->LastAccessTime.dwHighDateTime;
548         lpCacheEntryInfo->LastAccessTime.dwLowDateTime = pUrlEntry->LastAccessTime.dwLowDateTime;
549         lpCacheEntryInfo->LastModifiedTime.dwHighDateTime = pUrlEntry->LastModifiedTime.dwHighDateTime;
550         lpCacheEntryInfo->LastModifiedTime.dwLowDateTime = pUrlEntry->LastModifiedTime.dwLowDateTime;
551         DosDateTimeToFileTime(pUrlEntry->wLastSyncDate, pUrlEntry->wLastSyncTime, &lpCacheEntryInfo->LastSyncTime);
552     }
553
554     if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
555         ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
556     dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
557     dwRequiredSize += lenUrl + 1;
558     
559     if (*lpdwBufferSize >= dwRequiredSize)
560     {
561         lpCacheEntryInfo->lpszSourceUrlName = (LPSTR)lpCacheEntryInfo + dwRequiredSize - lenUrl - 1;
562         strcpy(lpCacheEntryInfo->lpszSourceUrlName, pUrlEntry->szSourceUrlName);
563     }
564
565     if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
566         ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
567     dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
568
569     lpszLocalFileName = (LPSTR)lpCacheEntryInfo + dwRequiredSize;
570     nLocalFilePathSize = *lpdwBufferSize - dwRequiredSize;
571     if (URLCache_LocalFileNameToPath(pHeader, (LPSTR)pUrlEntry + pUrlEntry->dwOffsetLocalName, pUrlEntry->CacheDir, lpszLocalFileName, &nLocalFilePathSize))
572     {
573         lpCacheEntryInfo->lpszLocalFileName = lpszLocalFileName;
574     }
575     dwRequiredSize += nLocalFilePathSize;
576
577     if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
578         ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
579     dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
580     dwRequiredSize += pUrlEntry->dwHeaderInfoSize + 1;
581
582     if (*lpdwBufferSize >= dwRequiredSize)
583     {
584         lpCacheEntryInfo->lpHeaderInfo = (LPSTR)lpCacheEntryInfo + dwRequiredSize - pUrlEntry->dwHeaderInfoSize - 1;
585         memcpy(lpCacheEntryInfo->lpHeaderInfo, (LPSTR)pUrlEntry + pUrlEntry->dwOffsetHeaderInfo, pUrlEntry->dwHeaderInfoSize);
586         ((LPBYTE)lpCacheEntryInfo)[dwRequiredSize - 1] = '\0';
587     }
588     if ((dwRequiredSize % 4) && (dwRequiredSize < *lpdwBufferSize))
589         ZeroMemory((LPBYTE)lpCacheEntryInfo + dwRequiredSize, 4 - (dwRequiredSize % 4));
590     dwRequiredSize = DWORD_ALIGN(dwRequiredSize);
591
592     if (dwRequiredSize > *lpdwBufferSize)
593     {
594         *lpdwBufferSize = dwRequiredSize;
595         SetLastError(ERROR_INSUFFICIENT_BUFFER);
596         return FALSE;
597     }
598     *lpdwBufferSize = dwRequiredSize;
599     return TRUE;
600 }
601
602 /***********************************************************************
603  *           URLCache_HashKey (Internal)
604  *
605  *  Returns the hash key for a given string
606  *
607  * RETURNS
608  *    hash key for the string
609  *
610  */
611 static DWORD URLCache_HashKey(LPCSTR lpszKey)
612 {
613     /* NOTE: this uses the same lookup table as SHLWAPI.UrlHash{A,W}
614      * but the algorithm and result are not the same!
615      */
616     static const unsigned char lookupTable[256] = 
617     {
618         0x01, 0x0E, 0x6E, 0x19, 0x61, 0xAE, 0x84, 0x77,
619         0x8A, 0xAA, 0x7D, 0x76, 0x1B, 0xE9, 0x8C, 0x33,
620         0x57, 0xC5, 0xB1, 0x6B, 0xEA, 0xA9, 0x38, 0x44,
621         0x1E, 0x07, 0xAD, 0x49, 0xBC, 0x28, 0x24, 0x41,
622         0x31, 0xD5, 0x68, 0xBE, 0x39, 0xD3, 0x94, 0xDF,
623         0x30, 0x73, 0x0F, 0x02, 0x43, 0xBA, 0xD2, 0x1C,
624         0x0C, 0xB5, 0x67, 0x46, 0x16, 0x3A, 0x4B, 0x4E,
625         0xB7, 0xA7, 0xEE, 0x9D, 0x7C, 0x93, 0xAC, 0x90,
626         0xB0, 0xA1, 0x8D, 0x56, 0x3C, 0x42, 0x80, 0x53,
627         0x9C, 0xF1, 0x4F, 0x2E, 0xA8, 0xC6, 0x29, 0xFE,
628         0xB2, 0x55, 0xFD, 0xED, 0xFA, 0x9A, 0x85, 0x58,
629         0x23, 0xCE, 0x5F, 0x74, 0xFC, 0xC0, 0x36, 0xDD,
630         0x66, 0xDA, 0xFF, 0xF0, 0x52, 0x6A, 0x9E, 0xC9,
631         0x3D, 0x03, 0x59, 0x09, 0x2A, 0x9B, 0x9F, 0x5D,
632         0xA6, 0x50, 0x32, 0x22, 0xAF, 0xC3, 0x64, 0x63,
633         0x1A, 0x96, 0x10, 0x91, 0x04, 0x21, 0x08, 0xBD,
634         0x79, 0x40, 0x4D, 0x48, 0xD0, 0xF5, 0x82, 0x7A,
635         0x8F, 0x37, 0x69, 0x86, 0x1D, 0xA4, 0xB9, 0xC2,
636         0xC1, 0xEF, 0x65, 0xF2, 0x05, 0xAB, 0x7E, 0x0B,
637         0x4A, 0x3B, 0x89, 0xE4, 0x6C, 0xBF, 0xE8, 0x8B,
638         0x06, 0x18, 0x51, 0x14, 0x7F, 0x11, 0x5B, 0x5C,
639         0xFB, 0x97, 0xE1, 0xCF, 0x15, 0x62, 0x71, 0x70,
640         0x54, 0xE2, 0x12, 0xD6, 0xC7, 0xBB, 0x0D, 0x20,
641         0x5E, 0xDC, 0xE0, 0xD4, 0xF7, 0xCC, 0xC4, 0x2B,
642         0xF9, 0xEC, 0x2D, 0xF4, 0x6F, 0xB6, 0x99, 0x88,
643         0x81, 0x5A, 0xD9, 0xCA, 0x13, 0xA5, 0xE7, 0x47,
644         0xE6, 0x8E, 0x60, 0xE3, 0x3E, 0xB3, 0xF6, 0x72,
645         0xA2, 0x35, 0xA0, 0xD7, 0xCD, 0xB4, 0x2F, 0x6D,
646         0x2C, 0x26, 0x1F, 0x95, 0x87, 0x00, 0xD8, 0x34,
647         0x3F, 0x17, 0x25, 0x45, 0x27, 0x75, 0x92, 0xB8,
648         0xA3, 0xC8, 0xDE, 0xEB, 0xF8, 0xF3, 0xDB, 0x0A,
649         0x98, 0x83, 0x7B, 0xE5, 0xCB, 0x4C, 0x78, 0xD1
650     };
651     BYTE key[4];
652     int i;
653     int subscript[sizeof(key) / sizeof(key[0])];
654
655     subscript[0] = *lpszKey;
656     subscript[1] = (char)(*lpszKey + 1);
657     subscript[2] = (char)(*lpszKey + 2);
658     subscript[3] = (char)(*lpszKey + 3);
659
660     for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
661         key[i] = lookupTable[i];
662
663     for (lpszKey++; *lpszKey && ((lpszKey[0] != '/') || (lpszKey[1] != 0)); lpszKey++)
664     {
665         for (i = 0; i < sizeof(key) / sizeof(key[0]); i++)
666             key[i] = lookupTable[*lpszKey ^ key[i]];
667     }
668
669     return *(DWORD *)key;
670 }
671
672 static inline HASH_CACHEFILE_ENTRY * URLCache_HashEntryFromOffset(LPCURLCACHE_HEADER pHeader, DWORD dwOffset)
673 {
674     return (HASH_CACHEFILE_ENTRY *)((LPBYTE)pHeader + dwOffset);
675 }
676
677 /***********************************************************************
678  *           URLCache_FindEntryInHash (Internal)
679  *
680  *  Searches all the hash tables in the index for the given URL and
681  * returns the entry, if it was found, in ppEntry
682  *
683  * RETURNS
684  *    TRUE if the entry was found
685  *    FALSE if the entry could not be found
686  *
687  */
688 static BOOL URLCache_FindEntryInHash(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl, CACHEFILE_ENTRY ** ppEntry)
689 {
690     /* structure of hash table:
691      *  448 entries divided into 64 blocks
692      *  each block therefore contains a chain of 7 key/offset pairs
693      * how position in table is calculated:
694      *  1. the url is hashed in helper function
695      *  2. the key % 64 * 8 is the offset
696      *  3. the key in the hash table is the hash key aligned to 64
697      *
698      * note:
699      *  there can be multiple hash tables in the file and the offset to
700      *  the next one is stored in the header of the hash table
701      */
702     DWORD key = URLCache_HashKey(lpszUrl);
703     DWORD offset = (key % HASHTABLE_NUM_ENTRIES) * sizeof(struct _HASH_ENTRY);
704     HASH_CACHEFILE_ENTRY * pHashEntry;
705     DWORD dwHashTableNumber = 0;
706
707     key = (DWORD)(key / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES;
708
709     for (pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHeader->dwOffsetFirstHashTable);
710          ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) >= ENTRY_START_OFFSET) && ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) < pHeader->dwFileSize);
711          pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHashEntry->dwAddressNext))
712     {
713         int i;
714         if (pHashEntry->dwHashTableNumber != dwHashTableNumber++)
715         {
716             ERR("Error: not right hash table number (%ld) expected %ld\n", pHashEntry->dwHashTableNumber, dwHashTableNumber);
717             continue;
718         }
719         /* make sure that it is in fact a hash entry */
720         if (pHashEntry->CacheFileEntry.dwSignature != HASH_SIGNATURE)
721         {
722             ERR("Error: not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->CacheFileEntry.dwSignature);
723             continue;
724         }
725
726         for (i = 0; i < HASHTABLE_BLOCKSIZE; i++)
727         {
728             struct _HASH_ENTRY * pHashElement = &pHashEntry->HashTable[offset + i];
729             if (key == (DWORD)(pHashElement->dwHashKey / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES)
730             {
731                 *ppEntry = (CACHEFILE_ENTRY *)((LPBYTE)pHeader + pHashElement->dwOffsetEntry);
732                 return TRUE;
733             }
734         }
735     }
736     return FALSE;
737 }
738
739 /***********************************************************************
740  *           URLCache_HashEntrySetUse (Internal)
741  *
742  *  Searches all the hash tables in the index for the given URL and
743  * sets the use count (stored or'ed with key)
744  *
745  * RETURNS
746  *    TRUE if the entry was found
747  *    FALSE if the entry could not be found
748  *
749  */
750 static BOOL URLCache_HashEntrySetUse(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl, DWORD dwUseCount)
751 {
752     /* see URLCache_FindEntryInHash for structure of hash tables */
753
754     DWORD key = URLCache_HashKey(lpszUrl);
755     DWORD offset = (key % HASHTABLE_NUM_ENTRIES) * sizeof(struct _HASH_ENTRY);
756     HASH_CACHEFILE_ENTRY * pHashEntry;
757     DWORD dwHashTableNumber = 0;
758
759     if (dwUseCount >= HASHTABLE_NUM_ENTRIES)
760     {
761         ERR("don't know what to do when use count exceeds %d, guessing\n", HASHTABLE_NUM_ENTRIES);
762         dwUseCount = HASHTABLE_NUM_ENTRIES - 1;
763     }
764
765     key = (DWORD)(key / HASHTABLE_NUM_ENTRIES) * HASHTABLE_BLOCKSIZE;
766
767     for (pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHeader->dwOffsetFirstHashTable);
768          ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) >= ENTRY_START_OFFSET) && ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) < pHeader->dwFileSize);
769          pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHashEntry->dwAddressNext))
770     {
771         int i;
772         if (pHashEntry->dwHashTableNumber != dwHashTableNumber++)
773         {
774             ERR("not right hash table number (%ld) expected %ld\n", pHashEntry->dwHashTableNumber, dwHashTableNumber);
775             continue;
776         }
777         /* make sure that it is in fact a hash entry */
778         if (pHashEntry->CacheFileEntry.dwSignature != HASH_SIGNATURE)
779         {
780             ERR("not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->CacheFileEntry.dwSignature);
781             continue;
782         }
783
784         for (i = 0; i < 7; i++)
785         {
786             struct _HASH_ENTRY * pHashElement = &pHashEntry->HashTable[offset + i];
787             if (key == (DWORD)(pHashElement->dwHashKey / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES)
788             {
789                 pHashElement->dwOffsetEntry = dwUseCount | (DWORD)(pHashElement->dwHashKey / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES;
790                 return TRUE;
791             }
792         }
793     }
794     return FALSE;
795 }
796
797 /***********************************************************************
798  *           URLCache_AddEntryToHash (Internal)
799  *
800  *  Searches all the hash tables for a free slot based on the offset
801  * generated from the hash key. If a free slot is found, the offset and
802  * key are entered into the hash table.
803  *
804  * RETURNS
805  *    TRUE if the entry was added
806  *    FALSE if the entry could not be added
807  *
808  */
809 static BOOL URLCache_AddEntryToHash(LPCURLCACHE_HEADER pHeader, LPCSTR lpszUrl, DWORD dwOffsetEntry)
810 {
811     /* see URLCache_FindEntryInHash for structure of hash tables */
812
813     DWORD key = URLCache_HashKey(lpszUrl);
814     DWORD offset = (key % HASHTABLE_NUM_ENTRIES) * sizeof(struct _HASH_ENTRY);
815     HASH_CACHEFILE_ENTRY * pHashEntry;
816     DWORD dwHashTableNumber = 0;
817
818     key = (DWORD)(key / HASHTABLE_NUM_ENTRIES) * HASHTABLE_NUM_ENTRIES;
819
820     for (pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHeader->dwOffsetFirstHashTable);
821          ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) >= ENTRY_START_OFFSET) && ((DWORD)((LPBYTE)pHashEntry - (LPBYTE)pHeader) < pHeader->dwFileSize);
822          pHashEntry = URLCache_HashEntryFromOffset(pHeader, pHashEntry->dwAddressNext))
823     {
824         int i;
825         if (pHashEntry->dwHashTableNumber != dwHashTableNumber++)
826         {
827             ERR("not right hash table number (%ld) expected %ld\n", pHashEntry->dwHashTableNumber, dwHashTableNumber);
828             break;
829         }
830         /* make sure that it is in fact a hash entry */
831         if (pHashEntry->CacheFileEntry.dwSignature != HASH_SIGNATURE)
832         {
833             ERR("not right signature (\"%.4s\") - expected \"HASH\"\n", (LPCSTR)&pHashEntry->CacheFileEntry.dwSignature);
834             break;
835         }
836
837         for (i = 0; i < 7; i++)
838         {
839             struct _HASH_ENTRY * pHashElement = &pHashEntry->HashTable[offset + i];
840             if (pHashElement->dwHashKey == 3 /* FIXME: just 3? */) /* if the slot is free */
841             {
842                 pHashElement->dwHashKey = key;
843                 pHashElement->dwOffsetEntry = dwOffsetEntry;
844                 return TRUE;
845             }
846         }
847     }
848     FIXME("need to create another hash table\n");
849     return FALSE;
850 }
851
852 /***********************************************************************
853  *           GetUrlCacheEntryInfoExA (WININET.@)
854  *
855  */
856 BOOL WINAPI GetUrlCacheEntryInfoExA(
857     LPCSTR lpszUrl,
858     LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
859     LPDWORD lpdwCacheEntryInfoBufSize,
860     LPSTR lpszReserved,
861     LPDWORD lpdwReserved,
862     LPVOID lpReserved,
863     DWORD dwFlags)
864 {
865     TRACE("(%s, %p, %p, %p, %p, %p, %lx)\n",
866         debugstr_a(lpszUrl), 
867         lpCacheEntryInfo,
868         lpdwCacheEntryInfoBufSize,
869         lpszReserved,
870         lpdwReserved,
871         lpReserved,
872         dwFlags);
873
874     if ((lpszReserved != NULL) ||
875         (lpdwReserved != NULL) ||
876         (lpReserved != NULL))
877     {
878         ERR("Reserved value was not 0\n");
879         SetLastError(ERROR_INVALID_PARAMETER);
880         return FALSE;
881     }
882     if (dwFlags != 0)
883         FIXME("Undocumented flag(s): %lx\n", dwFlags);
884     return GetUrlCacheEntryInfoA(lpszUrl, lpCacheEntryInfo, lpdwCacheEntryInfoBufSize);
885 }
886
887 /***********************************************************************
888  *           GetUrlCacheEntryInfoA (WININET.@)
889  *
890  */
891 BOOL WINAPI GetUrlCacheEntryInfoA(
892     IN LPCSTR lpszUrlName,
893     IN LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
894     IN OUT LPDWORD lpdwCacheEntryInfoBufferSize
895 )
896 {
897     LPURLCACHE_HEADER pHeader;
898     CACHEFILE_ENTRY * pEntry;
899     URL_CACHEFILE_ENTRY * pUrlEntry;
900
901     if (!URLCache_OpenIndex())
902         return FALSE;
903
904     if (!(pHeader = URLCache_LockIndex()))
905         return FALSE;
906
907     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
908     {
909         if (!URLCache_FindEntry(pHeader, lpszUrlName, &pEntry))
910         {
911             URLCache_UnlockIndex(pHeader);
912             WARN("entry %s not found!\n", debugstr_a(lpszUrlName));
913             SetLastError(ERROR_FILE_NOT_FOUND);
914             return FALSE;
915         }
916     }
917
918     /* FIXME: check signature */
919
920     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
921     TRACE("Found URL: %s\n", pUrlEntry->szSourceUrlName);
922     TRACE("Header info: %s\n", (LPBYTE)pUrlEntry + pUrlEntry->dwOffsetHeaderInfo);
923
924     if (!URLCache_CopyEntry(pHeader, lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize, pUrlEntry))
925     {
926         URLCache_UnlockIndex(pHeader);
927         return FALSE;
928     }
929     TRACE("Local File Name: %s\n", lpCacheEntryInfo->lpszLocalFileName);
930
931     URLCache_UnlockIndex(pHeader);
932
933     return TRUE;
934 }
935
936 BOOL WINAPI RetrieveUrlCacheEntryFileA(
937     IN LPCSTR lpszUrlName,
938     OUT LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo, 
939     IN OUT LPDWORD lpdwCacheEntryInfoBufferSize,
940     IN DWORD dwReserved
941     )
942 {
943     LPURLCACHE_HEADER pHeader;
944     CACHEFILE_ENTRY * pEntry;
945     URL_CACHEFILE_ENTRY * pUrlEntry;
946
947     if (!URLCache_OpenIndex())
948         return FALSE;
949
950     if (!(pHeader = URLCache_LockIndex()))
951         return FALSE;
952
953     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
954     {
955         if (!URLCache_FindEntry(pHeader, lpszUrlName, &pEntry))
956         {
957             URLCache_UnlockIndex(pHeader);
958             TRACE("entry %s not found!\n", lpszUrlName);
959             SetLastError(ERROR_FILE_NOT_FOUND);
960             return FALSE;
961         }
962     }
963
964     /* FIXME: check signature */
965
966     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
967     TRACE("Found URL: %s\n", pUrlEntry->szSourceUrlName);
968     TRACE("Header info: %s\n", (LPBYTE)pUrlEntry + pUrlEntry->dwOffsetHeaderInfo);
969
970     pUrlEntry->dwHitRate++;
971     pUrlEntry->dwUseCount++;
972     URLCache_HashEntrySetUse(pHeader, lpszUrlName, pUrlEntry->dwUseCount);
973
974     if (!URLCache_CopyEntry(pHeader, lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize, pUrlEntry))
975     {
976         URLCache_UnlockIndex(pHeader);
977         return FALSE;
978     }
979     TRACE("Local File Name: %s\n", lpCacheEntryInfo->lpszLocalFileName);
980
981     URLCache_UnlockIndex(pHeader);
982
983     return TRUE;
984 }
985
986 BOOL WINAPI UnlockUrlCacheEntryFileA(
987     IN LPCSTR lpszUrlName, 
988     IN DWORD dwReserved
989     )
990 {
991     LPURLCACHE_HEADER pHeader;
992     CACHEFILE_ENTRY * pEntry;
993     URL_CACHEFILE_ENTRY * pUrlEntry;
994
995     if (dwReserved)
996     {
997         ERR("dwReserved != 0\n");
998         SetLastError(ERROR_INVALID_PARAMETER);
999         return FALSE;
1000     }
1001
1002     if (!URLCache_OpenIndex())
1003         return FALSE;
1004
1005     if (!(pHeader = URLCache_LockIndex()))
1006         return FALSE;
1007
1008     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
1009     {
1010         if (!URLCache_FindEntry(pHeader, lpszUrlName, &pEntry))
1011         {
1012             URLCache_UnlockIndex(pHeader);
1013             TRACE("entry %s not found!\n", lpszUrlName);
1014             SetLastError(ERROR_FILE_NOT_FOUND);
1015             return FALSE;
1016         }
1017     }
1018
1019     /* FIXME: check signature */
1020
1021     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
1022
1023     if (pUrlEntry->dwUseCount == 0)
1024     {
1025         URLCache_UnlockIndex(pHeader);
1026         return FALSE;
1027     }
1028     pUrlEntry->dwUseCount--;
1029     URLCache_HashEntrySetUse(pHeader, lpszUrlName, pUrlEntry->dwUseCount);
1030
1031     URLCache_UnlockIndex(pHeader);
1032
1033     return TRUE;
1034 }
1035
1036 /***********************************************************************
1037  *           CreateUrlCacheEntryA (WININET.@)
1038  *
1039  */
1040 BOOL WINAPI CreateUrlCacheEntryA(
1041     IN LPCSTR lpszUrlName,
1042     IN DWORD dwExpectedFileSize,
1043     IN LPCSTR lpszFileExtension,
1044     OUT LPSTR lpszFileName,
1045     IN DWORD dwReserved
1046 )
1047 {
1048     LPURLCACHE_HEADER pHeader;
1049     CHAR szFile[MAX_PATH];
1050     CHAR szExtension[MAX_PATH];
1051     LPCSTR lpszUrlPart;
1052     LPCSTR lpszUrlEnd;
1053     LPCSTR lpszFileNameExtension;
1054     LPSTR lpszFileNameNoPath;
1055     int i;
1056     int countnoextension;
1057     BYTE CacheDir;
1058     LONG lBufferSize = MAX_PATH * sizeof(CHAR);
1059     BOOL bFound = FALSE;
1060     int count;
1061
1062     if (dwReserved)
1063     {
1064         ERR("dwReserved != 0\n");
1065         SetLastError(ERROR_INVALID_PARAMETER);
1066         return FALSE;
1067     }
1068
1069     for (lpszUrlEnd = lpszUrlName; *lpszUrlEnd; lpszUrlEnd++)
1070         ;
1071     
1072     if (((lpszUrlEnd - lpszUrlName) > 1) && (*(lpszUrlEnd - 1) == '/'))
1073         lpszUrlEnd--;
1074
1075     for (lpszUrlPart = lpszUrlEnd; 
1076         (lpszUrlPart >= lpszUrlName); 
1077         lpszUrlPart--)
1078     {
1079         if ((*lpszUrlPart == '/') && ((lpszUrlEnd - lpszUrlPart) > 1))
1080         {
1081             bFound = TRUE;
1082             lpszUrlPart++;
1083             break;
1084         }
1085     }
1086     if (!strcmp(lpszUrlPart, "www"))
1087     {
1088         lpszUrlPart += strlen("www");
1089     }
1090
1091     count = lpszUrlEnd - lpszUrlPart;
1092
1093     if (bFound && (count < MAX_PATH))
1094     {
1095         memcpy(szFile, lpszUrlPart, count * sizeof(CHAR));
1096         szFile[count] = '\0';
1097         /* FIXME: get rid of illegal characters like \, / and : */
1098     }
1099     else
1100     {
1101         FIXME("need to generate a random filename");
1102     }
1103
1104     TRACE("File name: %s\n", szFile);
1105
1106     if (!URLCache_OpenIndex())
1107         return FALSE;
1108
1109     if (!(pHeader = URLCache_LockIndex()))
1110         return FALSE;
1111
1112     CacheDir = (BYTE)(rand() % pHeader->DirectoryCount);
1113
1114     URLCache_LocalFileNameToPath(pHeader, szFile, CacheDir, lpszFileName, &lBufferSize);
1115
1116     URLCache_UnlockIndex(pHeader);
1117
1118     lpszFileNameNoPath = lpszFileName + strlen(szCacheContentPath) + DIR_LENGTH + 1;
1119
1120     countnoextension = strlen(lpszFileNameNoPath);
1121     lpszFileNameExtension = PathFindExtensionA(lpszFileNameNoPath);
1122     if (lpszFileNameExtension)
1123         countnoextension -= strlen(lpszFileNameExtension);
1124     *szExtension = '\0';
1125
1126     if (lpszFileExtension)
1127     {
1128         szExtension[0] = '.';
1129         strcpy(szExtension+1, lpszFileExtension);
1130     }
1131
1132     for (i = 0; i < 255; i++)
1133     {
1134         HANDLE hFile;
1135         strncpy(lpszFileNameNoPath, szFile, countnoextension);
1136         sprintf(lpszFileNameNoPath + countnoextension, "[%u]%s", i, szExtension);
1137         TRACE("Trying: %s\n", lpszFileName);
1138         hFile = CreateFileA(lpszFileName, GENERIC_READ, 0, NULL, CREATE_NEW, 0, NULL);
1139         if (hFile != INVALID_HANDLE_VALUE)
1140         {
1141             CloseHandle(hFile);
1142             return TRUE;
1143         }
1144     }
1145
1146     return FALSE;
1147 }
1148
1149 /***********************************************************************
1150  *           CommitUrlCacheEntryA (WININET.@)
1151  *
1152  */
1153 BOOL WINAPI CommitUrlCacheEntryA(
1154     IN LPCSTR lpszUrlName,
1155     IN LPCSTR lpszLocalFileName,
1156     IN FILETIME ExpireTime,
1157     IN FILETIME LastModifiedTime,
1158     IN DWORD CacheEntryType,
1159     IN LPBYTE lpHeaderInfo,
1160     IN DWORD dwHeaderSize,
1161     IN LPCSTR lpszFileExtension,
1162     IN LPCSTR dwReserved
1163     )
1164 {
1165     LPURLCACHE_HEADER pHeader;
1166     CACHEFILE_ENTRY * pEntry;
1167     URL_CACHEFILE_ENTRY * pUrlEntry;
1168     DWORD dwBytesNeeded = sizeof(*pUrlEntry) - sizeof(pUrlEntry->szSourceUrlName);
1169     BYTE cDirectory;
1170     BOOL bFound = FALSE;
1171     DWORD dwOffsetLocalFileName;
1172     DWORD dwOffsetHeader;
1173     DWORD dwFileSizeLow;
1174     DWORD dwFileSizeHigh;
1175     HANDLE hFile;
1176
1177     TRACE("(%s, %s, ..., ..., %lx, %p, %ld, %s, %p)\n",
1178         debugstr_a(lpszUrlName),
1179         debugstr_a(lpszLocalFileName),
1180         CacheEntryType,
1181         lpHeaderInfo,
1182         dwHeaderSize,
1183         lpszFileExtension,
1184         dwReserved);
1185
1186     if (dwReserved)
1187     {
1188         ERR("dwReserved != 0\n");
1189         SetLastError(ERROR_INVALID_PARAMETER);
1190         return FALSE;
1191     }
1192     if (lpHeaderInfo == NULL)
1193     {
1194         FIXME("lpHeaderInfo == NULL - will crash at the moment\n");
1195     }
1196
1197     hFile = CreateFileA(lpszLocalFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
1198     if (hFile == INVALID_HANDLE_VALUE)
1199     {
1200         ERR("couldn't open file (error is %ld)\n", GetLastError());
1201         return FALSE;
1202     }
1203     
1204     /* Get file size */
1205     dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
1206     if ((dwFileSizeLow == -1) && (GetLastError() != NO_ERROR))
1207     {
1208         ERR("couldn't get file size (error is %ld)\n", GetLastError());
1209         CloseHandle(hFile);
1210         return FALSE;
1211     }
1212
1213     CloseHandle(hFile);
1214
1215     if (!URLCache_OpenIndex())
1216         return FALSE;
1217
1218     if (!(pHeader = URLCache_LockIndex()))
1219         return FALSE;
1220
1221     if (URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry) || URLCache_FindEntry(pHeader, lpszUrlName, &pEntry))
1222     {
1223         URLCache_UnlockIndex(pHeader);
1224         FIXME("entry already in cache - don't know what to do!\n");
1225         SetLastError(ERROR_FILE_NOT_FOUND);
1226         return FALSE;
1227     }
1228
1229     if (memcmp(lpszLocalFileName, szCacheContentPath, strlen(szCacheContentPath)))
1230     {
1231         URLCache_UnlockIndex(pHeader);
1232         ERR("path must begin with cache content path\n");
1233         SetLastError(ERROR_INVALID_PARAMETER);
1234         return FALSE;
1235     }
1236
1237     lpszLocalFileName += strlen(szCacheContentPath);
1238
1239     for (cDirectory = 0; cDirectory < pHeader->DirectoryCount; cDirectory++)
1240     {
1241         if (!strncmp(pHeader->directory_data[cDirectory].filename, lpszLocalFileName, DIR_LENGTH))
1242         {
1243             bFound = TRUE;
1244             break;
1245         }
1246     }
1247
1248     if (!bFound)
1249     {
1250         URLCache_UnlockIndex(pHeader);
1251         ERR("cache directory not found in path %s\n", lpszLocalFileName);
1252         SetLastError(ERROR_INVALID_PARAMETER);
1253         return FALSE;
1254     }
1255
1256     lpszLocalFileName += (DIR_LENGTH + 1); /* "1234WXYZ\" */
1257
1258     dwOffsetLocalFileName = DWORD_ALIGN(dwBytesNeeded + strlen(lpszUrlName) + 1); /* + 1 for NULL terminating char */
1259     dwOffsetHeader = DWORD_ALIGN(dwOffsetLocalFileName + strlen(lpszLocalFileName) + 1);
1260     dwBytesNeeded = DWORD_ALIGN(dwBytesNeeded + dwHeaderSize);
1261
1262     if (dwBytesNeeded % BLOCKSIZE)
1263     {
1264         dwBytesNeeded -= dwBytesNeeded % BLOCKSIZE;
1265         dwBytesNeeded += BLOCKSIZE;
1266     }
1267
1268     if (!URLCache_FindFirstFreeEntry(pHeader, dwBytesNeeded / BLOCKSIZE, &pEntry))
1269     {
1270         /* we should grow the index file here */
1271         URLCache_UnlockIndex(pHeader);
1272         FIXME("no free entries\n");
1273         return FALSE;
1274     }
1275
1276     /* FindFirstFreeEntry fills in blocks used */
1277     pUrlEntry = (URL_CACHEFILE_ENTRY *)pEntry;
1278     pUrlEntry->CacheFileEntry.dwSignature = URL_SIGNATURE;
1279     pUrlEntry->CacheDir = cDirectory;
1280     pUrlEntry->CacheEntryType = CacheEntryType;
1281     pUrlEntry->dwHeaderInfoSize = dwHeaderSize;
1282     pUrlEntry->dwExemptDelta = 0;
1283     pUrlEntry->dwHitRate = 0;
1284     pUrlEntry->dwOffsetHeaderInfo = dwOffsetHeader;
1285     pUrlEntry->dwOffsetLocalName = dwOffsetLocalFileName;
1286     pUrlEntry->dwOffsetUrl = sizeof(*pUrlEntry) - sizeof(pUrlEntry->szSourceUrlName);
1287     pUrlEntry->dwSizeHigh = 0;
1288     pUrlEntry->dwSizeLow = dwFileSizeLow;
1289     pUrlEntry->dwSizeHigh = dwFileSizeHigh;
1290     pUrlEntry->dwUseCount = 0;
1291     GetSystemTimeAsFileTime(&pUrlEntry->LastAccessTime);
1292     pUrlEntry->LastModifiedTime = LastModifiedTime;
1293     FileTimeToDosDateTime(&pUrlEntry->LastAccessTime, &pUrlEntry->wLastSyncDate, &pUrlEntry->wLastSyncTime);
1294     FileTimeToDosDateTime(&ExpireTime, &pUrlEntry->wExpiredDate, &pUrlEntry->wExpiredTime);
1295     pUrlEntry->wUnknownDate = pUrlEntry->wLastSyncDate;
1296     pUrlEntry->wUnknownTime = pUrlEntry->wLastSyncTime;
1297
1298     /*** Unknowns ***/
1299     pUrlEntry->dwUnknown1 = 0;
1300     pUrlEntry->dwUnknown2 = 0;
1301     pUrlEntry->dwUnknown3 = 0x60;
1302     pUrlEntry->Unknown4 = 0;
1303     pUrlEntry->wUnknown5 = 0x1010;
1304     pUrlEntry->dwUnknown6 = 0;
1305     pUrlEntry->dwUnknown7 = 0;
1306     pUrlEntry->dwUnknown8 = 0;
1307
1308     strcpy(pUrlEntry->szSourceUrlName, lpszUrlName);
1309     strcpy((LPSTR)((LPBYTE)pUrlEntry + dwOffsetLocalFileName), lpszLocalFileName);
1310     memcpy((LPSTR)((LPBYTE)pUrlEntry + dwOffsetHeader), lpHeaderInfo, dwHeaderSize);
1311
1312     if (!URLCache_AddEntryToHash(pHeader, lpszUrlName, (DWORD)((LPBYTE)pUrlEntry - (LPBYTE)pHeader)))
1313     {
1314         URLCache_UnlockIndex(pHeader);
1315         return FALSE;
1316     }
1317
1318     URLCache_UnlockIndex(pHeader);
1319
1320     return TRUE;
1321 }
1322
1323 BOOL WINAPI ReadUrlCacheEntryStream(
1324     IN HANDLE hUrlCacheStream,
1325     IN  DWORD dwLocation,
1326     IN OUT LPVOID lpBuffer,
1327     IN OUT LPDWORD lpdwLen,
1328     IN DWORD dwReserved
1329     )
1330 {
1331     /* Get handle to file from 'stream' */
1332     STREAM_HANDLE * pStream = (STREAM_HANDLE *)hUrlCacheStream;
1333
1334     if (dwReserved != 0)
1335     {
1336         ERR("dwReserved != 0\n");
1337         SetLastError(ERROR_INVALID_PARAMETER);
1338         return FALSE;
1339     }
1340
1341     if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->lpszUrl, INTERNET_MAX_URL_LENGTH))
1342     {
1343         SetLastError(ERROR_INVALID_HANDLE);
1344         return FALSE;
1345     }
1346
1347     if (SetFilePointer(pStream->hFile, dwLocation, NULL, FILE_CURRENT) == -1)
1348         return FALSE;
1349     return ReadFile(pStream->hFile, lpBuffer, *lpdwLen, lpdwLen, NULL);
1350 }
1351
1352 HANDLE WINAPI RetrieveUrlCacheEntryStreamA(
1353     IN LPCSTR lpszUrlName,
1354     OUT LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo,
1355     IN OUT LPDWORD lpdwCacheEntryInfoBufferSize,
1356     IN BOOL fRandomRead,
1357     IN DWORD dwReserved
1358     )
1359 {
1360     /* NOTE: this is not the same as the way that the native
1361      * version allocates 'stream' handles. I did it this way
1362      * as it is much easier and no applications should depend
1363      * on this behaviour. (Native version appears to allocate
1364      * indices into a table)
1365      */
1366     STREAM_HANDLE * pStream;
1367     HANDLE hFile;
1368
1369     if (!RetrieveUrlCacheEntryFileA(lpszUrlName, 
1370         lpCacheEntryInfo, 
1371         lpdwCacheEntryInfoBufferSize,
1372         dwReserved))
1373     {
1374         return NULL;
1375     }
1376     
1377     hFile = CreateFileA(lpCacheEntryInfo->lpszLocalFileName,
1378         GENERIC_READ,
1379         FILE_SHARE_READ,
1380         NULL,
1381         OPEN_EXISTING,
1382         fRandomRead ? FILE_FLAG_RANDOM_ACCESS : 0,
1383         NULL);
1384     if (hFile == INVALID_HANDLE_VALUE)
1385         return FALSE;
1386     
1387     /* allocate handle storage space */
1388     pStream = (STREAM_HANDLE *)HeapAlloc(GetProcessHeap(), 0, sizeof(STREAM_HANDLE) + strlen(lpszUrlName) * sizeof(CHAR));
1389     if (!pStream)
1390     {
1391         CloseHandle(hFile);
1392         SetLastError(ERROR_OUTOFMEMORY);
1393         return FALSE;
1394     }
1395
1396     pStream->hFile = hFile;
1397     strcpy(pStream->lpszUrl, lpszUrlName);
1398     return (HANDLE)pStream;
1399 }
1400
1401 BOOL WINAPI UnlockUrlCacheEntryStream(
1402     IN HANDLE hUrlCacheStream,
1403     IN DWORD dwReserved
1404 )
1405 {
1406     STREAM_HANDLE * pStream = (STREAM_HANDLE *)hUrlCacheStream;
1407
1408     if (dwReserved != 0)
1409     {
1410         ERR("dwReserved != 0\n");
1411         SetLastError(ERROR_INVALID_PARAMETER);
1412         return FALSE;
1413     }
1414
1415     if (IsBadReadPtr(pStream, sizeof(*pStream)) || IsBadStringPtrA(pStream->lpszUrl, INTERNET_MAX_URL_LENGTH))
1416     {
1417         SetLastError(ERROR_INVALID_HANDLE);
1418         return FALSE;
1419     }
1420
1421     if (!UnlockUrlCacheEntryFileA(pStream->lpszUrl, 0))
1422         return FALSE;
1423
1424     /* close file handle */
1425     CloseHandle(pStream->hFile);
1426
1427     /* free allocated space */
1428     HeapFree(GetProcessHeap(), 0, pStream);
1429
1430     return TRUE;
1431 }
1432
1433 BOOL WINAPI DeleteUrlCacheEntryA(LPCSTR lpszUrlName)
1434 {
1435     LPURLCACHE_HEADER pHeader;
1436     CACHEFILE_ENTRY * pEntry;
1437     DWORD dwStartBlock;
1438     DWORD dwBlock;
1439     BYTE * AllocationTable;
1440
1441     if (!URLCache_OpenIndex())
1442         return FALSE;
1443
1444     if (!(pHeader = URLCache_LockIndex()))
1445         return FALSE;
1446
1447     if (!URLCache_FindEntryInHash(pHeader, lpszUrlName, &pEntry))
1448     {
1449         if (!URLCache_FindEntry(pHeader, lpszUrlName, &pEntry))
1450         {
1451             URLCache_UnlockIndex(pHeader);
1452             TRACE("entry %s not found!\n", lpszUrlName);
1453             SetLastError(ERROR_FILE_NOT_FOUND);
1454             return FALSE;
1455         }
1456     }
1457
1458     AllocationTable = (LPBYTE)pHeader + ALLOCATION_TABLE_OFFSET;
1459
1460     /* update allocation table */
1461     dwStartBlock = ((DWORD)pEntry - (DWORD)pHeader) / BLOCKSIZE;
1462     for (dwBlock = dwStartBlock; dwBlock < dwStartBlock + pEntry->dwBlocksUsed; dwBlock++)
1463         URLCache_Allocation_BlockFree(AllocationTable, dwBlock);
1464
1465     URLCache_DeleteEntry(pEntry);
1466
1467     /* FIXME: update hash table */
1468
1469     URLCache_UnlockIndex(pHeader);
1470
1471     return TRUE;
1472 }
1473
1474 INTERNETAPI GROUPID WINAPI CreateUrlCacheGroup(DWORD dwFlags, LPVOID 
1475 lpReserved)
1476 {
1477   FIXME("(%lx, %p): stub\n", dwFlags, lpReserved);
1478   return FALSE;
1479 }
1480
1481 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryA(LPCSTR lpszUrlSearchPattern,
1482  LPINTERNET_CACHE_ENTRY_INFOA lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
1483 {
1484   FIXME("(%s, %p, %p): stub\n", debugstr_a(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
1485   return 0;
1486 }
1487
1488 INTERNETAPI HANDLE WINAPI FindFirstUrlCacheEntryW(LPCWSTR lpszUrlSearchPattern,
1489  LPINTERNET_CACHE_ENTRY_INFOW lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize)
1490 {
1491   FIXME("(%s, %p, %p): stub\n", debugstr_w(lpszUrlSearchPattern), lpFirstCacheEntryInfo, lpdwFirstCacheEntryInfoBufferSize);
1492   return 0;
1493 }
1494
1495 BOOL WINAPI DeleteUrlCacheGroup(GROUPID GroupId, DWORD dwFlags, LPVOID lpReserved)
1496 {
1497     FIXME("STUB\n");
1498     return FALSE;
1499 }
1500
1501 BOOL WINAPI SetUrlCacheEntryGroup(LPCSTR lpszUrlName, DWORD dwFlags,
1502   GROUPID GroupId, LPBYTE pbGroupAttributes, DWORD cbGroupAttributes,
1503   LPVOID lpReserved)
1504 {
1505     FIXME("STUB\n");
1506     SetLastError(ERROR_FILE_NOT_FOUND);
1507     return FALSE;
1508 }
1509
1510 /***********************************************************************
1511  *           GetUrlCacheEntryInfoW (WININET.@)
1512  *
1513  */
1514 BOOL WINAPI GetUrlCacheEntryInfoW(LPCWSTR lpszUrl,
1515   LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntry,
1516   LPDWORD lpCacheEntrySize)
1517 {
1518     FIXME("(%s) stub\n",debugstr_w(lpszUrl));
1519     SetLastError(ERROR_FILE_NOT_FOUND);
1520     return FALSE;
1521 }
1522
1523 /***********************************************************************
1524  *           GetUrlCacheEntryInfoExW (WININET.@)
1525  *
1526  */
1527 BOOL WINAPI GetUrlCacheEntryInfoExW(
1528     LPCWSTR lpszUrl,
1529     LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo,
1530     LPDWORD lpdwCacheEntryInfoBufSize,
1531     LPWSTR lpszReserved,
1532     LPDWORD lpdwReserved,
1533     LPVOID lpReserved,
1534     DWORD dwFlags)
1535 {
1536     FIXME(" url=%s, flags=%ld\n",debugstr_w(lpszUrl),dwFlags);
1537     INTERNET_SetLastError(ERROR_FILE_NOT_FOUND);
1538     return FALSE;
1539 }
1540
1541 /***********************************************************************
1542  *           GetUrlCacheConfigInfoA (WININET.@)
1543  *
1544  * CacheInfo is some CACHE_CONFIG_INFO structure, with no MS info found by google
1545  */
1546 BOOL WINAPI GetUrlCacheConfigInfoA(LPDWORD CacheInfo, LPDWORD size, DWORD bitmask)
1547 {
1548     FIXME("(%p, %p, %lx)\n", CacheInfo, size, bitmask);
1549     INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
1550     return FALSE;
1551 }
1552
1553 /***********************************************************************
1554  *           GetUrlCacheConfigInfoW (WININET.@)
1555  */
1556 BOOL WINAPI GetUrlCacheConfigInfoW(LPDWORD CacheInfo, LPDWORD size, DWORD bitmask)
1557 {
1558     FIXME("(%p, %p, %lx)\n", CacheInfo, size, bitmask);
1559     INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
1560     return FALSE;
1561 }
1562
1563
1564 /***********************************************************************
1565  *           SetUrlCacheEntryInfoA (WININET.@)
1566  */
1567 BOOL WINAPI SetUrlCacheEntryInfoA(LPCSTR lpszUrlName, LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntryInfo, DWORD dwFieldControl)
1568 {
1569     FIXME("stub\n");
1570     return FALSE;
1571 }
1572
1573 /***********************************************************************
1574  *           SetUrlCacheEntryInfoW (WININET.@)
1575  */
1576 BOOL WINAPI SetUrlCacheEntryInfoW(LPCWSTR lpszUrlName, LPINTERNET_CACHE_ENTRY_INFOW lpCacheEntryInfo, DWORD dwFieldControl)
1577 {
1578     FIXME("stub\n");
1579     return FALSE;
1580 }