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