/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    cachapia.cxx

Abstract:

    contains the ANSI version of cache mangemant APIs.

Author:

    Madan Appiah (madana)  12-Dec-1994

Environment:

    User Mode - Win32

Revision History:

--*/

#include <cache.hxx>


/*-----------------------------------------------------------------------------
CreateContainer
----------------------------------------------------------------------------*/
URLCACHEAPI_(BOOL) CreateUrlCacheContainerA(
                 IN LPCSTR Name, 
                 IN LPCSTR CachePrefix, 
                 IN LPCSTR CachePath, 
                 IN DWORD KBCacheLimit,
                 IN DWORD dwContainerType,
                 IN DWORD dwOptions,
                 IN OUT LPVOID pvBuffer,
                 IN OUT LPDWORD cbBuffer
)
{
    ENTER_CACHE_API ((DBG_API, Bool, "CreateUrlCacheContainerA", "%q, %q, %q, %d, %d, %d, %#x, %#x",
        Name, CachePrefix, CachePath, KBCacheLimit, dwContainerType, dwOptions, pvBuffer, cbBuffer));

    DWORD Error;

    // Initialize globals
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->CreateContainer(
                        Name,
                        CachePrefix,
                        CachePath,
                        KBCacheLimit,
                        dwOptions);

    LEAVE_CACHE_API();
}

URLCACHEAPI_(BOOL) DeleteUrlCacheContainerA(
IN LPCSTR Name,
IN DWORD dwOptions)
{
    ENTER_CACHE_API ((DBG_API, Bool, "DeleteContainerA", "%q, %d", Name, dwOptions));

    DWORD Error;

    // Initialize globals
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->DeleteContainer(
                        Name,
                        0
                        );

    LEAVE_CACHE_API();
}


URLCACHEAPI_(HANDLE) FindFirstUrlCacheContainerA(
    IN OUT LPDWORD pdwModified,
    OUT LPINTERNET_CACHE_CONTAINER_INFOA lpContainerInfo,
    IN OUT LPDWORD lpdwContainerInfoBufferSize,
    IN DWORD dwOptions
)
{
    ENTER_CACHE_API ((DBG_API, Bool, "FindFirstContainerA",
        "%#x, %#x, %#x, %#x",
        pdwModified,
        lpContainerInfo,
        lpdwContainerInfoBufferSize,
        dwOptions
    ));

    DWORD Error;
    HANDLE hFind = NULL;


    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    hFind = GlobalUrlContainers->FindFirstContainer(pdwModified, 
        lpContainerInfo, lpdwContainerInfoBufferSize, dwOptions);

    if (hFind)
        Error = ERROR_SUCCESS;
    else
    {
        Error = GetLastError();
        // BUGBUG: Free hFind?
        // does the free take NULL?
    }

Cleanup:
    if( Error != ERROR_SUCCESS )
    {
        SetLastError( Error );
        DEBUG_ERROR(API, Error);
    }

    DEBUG_LEAVE_API (hFind);
    return hFind;
}

    
URLCACHEAPI_(BOOL) FindNextUrlCacheContainerA(
IN HANDLE hFind, 
OUT LPINTERNET_CACHE_CONTAINER_INFOA lpContainerInfo,
IN OUT LPDWORD lpdwContainerInfoBufferSize
)
{
    ENTER_CACHE_API ((DBG_API, Bool, "FindNextContainerA",
        "%#x, %#x, %#x",
        hFind, 
        lpContainerInfo,
        lpdwContainerInfoBufferSize
    ));

    DWORD Error;
    DWORD i;


    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }


    if (GlobalUrlContainers->FindNextContainer(hFind, 
            lpContainerInfo, lpdwContainerInfoBufferSize))
        Error = ERROR_SUCCESS;
    else
        Error = GetLastError();

    LEAVE_CACHE_API();
}

#define GZIPHACK    8624

URLCACHEAPI_(BOOL) CreateUrlCacheEntryA(
    IN LPCSTR   lpszUrlName,
    IN DWORD    dwExpectedFileSize,
    IN LPCSTR   lpszFileExtension,
    OUT LPSTR lpszFileName,
    IN DWORD dwReserved
    )
/*++

Routine Description:

    This function creates a temperary file in the cache storage. This call
    is called by the application when it receives a url file from a
    server. When the receive is completed it caches this file to url cache
    management, which will move the file to permanent cache file. The idea
    is the cache file is written only once directly into the cache store.

Arguments:

    lpszUrlName : name of the url file (unused now).

    lpszFileExtension: File extension for the saved data file

    dwExpectedFileSize : expected size of the incoming file. If it is unknown
        this value is set to null.

    lpszFileName : pointer to a buffer that receives the full path name of
        the the temp file.

    dwReserved : reserved for future use.

Return Value:

    Windows Error Code.

--*/
{
    ENTER_CACHE_API ((DBG_API, Bool, "CreateUrlCacheEntryA", "%q, %q, %d, %q, %#x",
        lpszUrlName, lpszFileExtension, dwExpectedFileSize, lpszFileName, dwReserved));

    DWORD Error;

    //
    // validate parameters.
    //

    if( IsBadUrl( lpszUrlName ) || IsBadWriteFileName( lpszFileName )  ) {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    // Null first char in lpszFileName cues CreateUniqueFile
    // to generate a file name from scratch. Otherwise,
    // an attempt will be made to generate the filename
    // using the contents of the buffer.
    if(dwReserved != GZIPHACK)
    {
        *lpszFileName = '\0';
    }

    Error = GlobalUrlContainers->CreateUniqueFile(
                        lpszUrlName,
                        dwExpectedFileSize,
                        lpszFileExtension,
                        lpszFileName, 
                        NULL
                        );

    LEAVE_CACHE_API();
}

URLCACHEAPI_(BOOL) CommitUrlCacheEntryA(
    IN LPCSTR lpszUrlName,
    IN LPCSTR lpszLocalFileName,
    IN FILETIME ExpireTime,
    IN FILETIME LastModifiedTime,
    IN DWORD CacheEntryType,
    IN LPBYTE lpHeaderInfo,
    IN DWORD dwHeaderSize,
    IN LPCSTR lpszFileExtension,
    IN LPCSTR lpszOriginalUrl
    )

/*++

Routine Description:

    This API caches a specified URL in the internet service  cache
    storage. It creates a database entry of the URL info and moves the
    URL file to cache storage.

Arguments:

    lpszUrlName : name of the URL that is cached.

    lpszLocalFileName : name of the local file where the URL data is
        stored. This file will be moved to an another file in cache storage, so
        this name is invalid after this api successfully returns. The
        name should include full path.

    ExpireTime : Expire time (GMT) of the file being cached. If it is
        unknown set it to zero.

    LastModifiedTime : Last modified time of this file. if this value is
        zero, current time is set as the last modified time.

    CacheEntryType : type of this new entry.

    lpHeaderInfo : if this pointer is non-NULL, it stores the HeaderInfo
        data as part of the URL entry in the memory mapped file, otherwise
        the app may store it else where. The size of the header info is
        specified by the HeaderSize parameter.

    dwHeaderSize : size of the header info associated with this URL, this
        can be non-zero even if the HeaderInfo specified above is NULL.

    lpszFileExtension :  file extension used to create this file.

    dwReserved : reserved for future use.

Return Value:

    Windows Error code.

--*/
{
    ENTER_CACHE_API ((DBG_API, Bool, "CommitUrlCacheEntryA",
        "%q, %q, <expires>, <last-mod>, %d, %#x, %d, %q, %q",
        lpszUrlName,
        lpszLocalFileName,
        CacheEntryType,
        lpHeaderInfo,
        dwHeaderSize,
        lpszFileExtension,
        lpszOriginalUrl
    ));

    DWORD Error;

    // validate parameters.
    if( IsBadUrl( lpszUrlName ) ||
        ( lpszLocalFileName ? IsBadReadFileName( lpszLocalFileName ) : FALSE ) ) 
    {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    if( lpHeaderInfo != NULL ) 
    {
        if( IsBadReadPtr(lpHeaderInfo, dwHeaderSize) ) 
        {
            Error =  ERROR_INVALID_PARAMETER;
            goto Cleanup;
        }
    }

    if( lpszFileExtension != NULL ) 
    {
        if( IsBadReadPtr(lpszFileExtension, 3) ) 
        {
            Error =  ERROR_INVALID_PARAMETER;
            goto Cleanup;
        }
    }

    FILETIME     ftPostCheck;
    ftPostCheck.dwLowDateTime = 0;
    ftPostCheck.dwHighDateTime = 0; 
  
    // Record args in structure.
    AddUrlArg Args;
    memset(&Args, 0, sizeof(Args));
    Args.pszUrl      = lpszUrlName;
    Args.pszFilePath = lpszLocalFileName;
    Args.dwFileSize  = 0;
    Args.qwExpires   = FT2LL(ExpireTime);
    Args.qwLastMod   = FT2LL(LastModifiedTime);
    Args.qwPostCheck = FT2LL(ftPostCheck);
    Args.ftCreate = LastModifiedTime;
    Args.dwEntryType = CacheEntryType;
    Args.pbHeaders   = (LPSTR)lpHeaderInfo;
    Args.cbHeaders   = dwHeaderSize;
    Args.pszFileExt  = lpszFileExtension;
    Args.pszRedirect = lpszOriginalUrl ? (LPSTR) lpszOriginalUrl : NULL;
    Args.fImage      = FALSE;

    Error = UrlCacheCommitFile(&Args);

    LEAVE_CACHE_API();
}



URLCACHEAPI_(BOOL) RetrieveUrlCacheEntryFileA(
    IN LPCSTR  lpszUrlName,
    OUT LPCACHE_ENTRY_INFOA lpCacheEntryInfo,
    IN OUT LPDWORD lpdwCacheEntryInfoBufferSize,
    IN DWORD dwReserved
    )
/*++

Routine Description:

    This API retrieves the specified URL file. When the file is retrieved
    it also checked out to the user to use. The user has to call
    UnlockUrlFile when he/she finished using it.

Arguments:

    lpszUrlName : name of the URL that is being retrieved.

    lpCacheEntryInfo : pointer to the url info structure that receives the url
        info.

    lpdwCacheEntryInfoBufferSize : pointer to a location where length of
        the above buffer is passed in. On return, this contains the length
        of the above buffer that is fulled in.

    dwReserved : reserved for future use.

Return Value:

    Windows Error code.

--*/
{
    ENTER_CACHE_API ((DBG_API, Bool, "RetrieveUrlCacheEntryFileA","%q, %#x, %#x, %#x",
        lpszUrlName, lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize, dwReserved));
        
    DWORD Error;

    // validate parameters.
    if( IsBadUrl( lpszUrlName ) ||
            IsBadWriteUrlInfo(
                lpCacheEntryInfo,
                *lpdwCacheEntryInfoBufferSize) ) {

        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->RetrieveUrl(
                        lpszUrlName,
                        (lpCacheEntryInfo ? &lpCacheEntryInfo : NULL),
                        lpdwCacheEntryInfoBufferSize,
                        LOOKUP_URL_CREATE,
                        RETRIEVE_WITH_CHECKS);

    LEAVE_CACHE_API();
}

URLCACHEAPI_(HANDLE) RetrieveUrlCacheEntryStreamA(
    IN LPCSTR  lpszUrlName,
    OUT LPCACHE_ENTRY_INFOA lpCacheEntryInfo,
    IN OUT LPDWORD lpdwCacheEntryInfoBufferSize,
    IN BOOL fRandomRead,
    IN DWORD dwReserved
    )
/*++

Routine Description:

    This API retrieves the specified URL file. When the file is retrieved
    it also checked out to the user to use. The user has to call
    UnlockUrlFile when he/she finished using it.

Arguments:

    lpszUrlName : name of the URL that is being retrieved.

    lpCacheEntryInfo : pointer to the url info structure that receives the url
        info.

    lpdwCacheEntryInfoBufferSize : pointer to a location where length of
        the above buffer is passed in. On return, this contains the length
        of the above buffer that is fulled in.

    fRandomRead : if this flag is set to TRUE, then stream is open for
        random access.

    dwReserved: must pass 0

Return Value:

    Windows Error code.

--*/
{
    ENTER_CACHE_API ((DBG_API, Handle, "RetrieveUrlCacheEntryStreamA",
        "%q, %#x, %#x, %d, %#x",
        lpszUrlName,
        lpCacheEntryInfo,
        lpdwCacheEntryInfoBufferSize,
        fRandomRead,
        dwReserved
    ));

    BOOL fLocked = FALSE;
    HANDLE hStream = NULL;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    DWORD Error, dwFileSize;

    // Validate parameters.
    if(   IsBadUrl( lpszUrlName )
       || IsBadWriteUrlInfo(lpCacheEntryInfo, *lpdwCacheEntryInfoBufferSize))
    {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }
    
    Error = GlobalUrlContainers->RetrieveUrl
        (lpszUrlName, &lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize,
            LOOKUP_URL_NOCREATE, RETRIEVE_WITHOUT_CHECKS);

    if( Error != ERROR_SUCCESS )
        goto Cleanup;
    fLocked = TRUE;        

    // Allocate a stream handle.
    CACHE_STREAM_CONTEXT_HANDLE* pStream;
    LOCK_CACHE();
    hStream = HandleMgr.Alloc (sizeof(CACHE_STREAM_CONTEXT_HANDLE));
    if (hStream)
    {        
        pStream = (CACHE_STREAM_CONTEXT_HANDLE*) HandleMgr.Map (hStream);
        INET_ASSERT (pStream);
    }
    UNLOCK_CACHE();
    if (!hStream)
    {
        Error = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    // Open the file.
    hFile = CreateFile
    (
        lpCacheEntryInfo->lpszLocalFileName,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL |
          (fRandomRead ? FILE_FLAG_RANDOM_ACCESS : FILE_FLAG_SEQUENTIAL_SCAN),
                // improves file read (cache) performance?
        NULL
    );
    if( hFile == INVALID_HANDLE_VALUE )
    {
        Error = GetLastError();
        goto Cleanup;
    }

    dwFileSize = GetFileSize(hFile, NULL);

    if (dwFileSize != lpCacheEntryInfo->dwSizeLow) 
    {
        Error = (dwFileSize==0xFFFFFFFF) ? GetLastError() : ERROR_INVALID_DATA;
        goto Cleanup;
    }

    pStream->FileHandle = hFile;

    // Copy URL name storage.
    pStream->SourceUrlName = NewString(lpszUrlName);
    if( !pStream->SourceUrlName)
    {
        Error = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }
    Error = ERROR_SUCCESS;

Cleanup:

    if( Error != ERROR_SUCCESS )
    {
        if (hStream)
        {
            HandleMgr.Free (hStream);
            hStream = NULL;
        }
        if (hFile)
            CloseHandle (hFile);
        if (fLocked)
            GlobalUrlContainers->UnlockUrl(lpszUrlName);
        SetLastError (Error);
        DEBUG_ERROR(API, Error);
    }

    DEBUG_LEAVE_API (hStream);
    return hStream;
}


URLCACHEAPI_(BOOL) GetUrlCacheEntryInfoA(
    IN LPCSTR lpszUrlName,
    OUT LPCACHE_ENTRY_INFOA lpCacheEntryInfo,
    IN OUT LPDWORD lpdwCacheEntryInfoBufferSize
    )
/*++

Routine Description:

    This function retrieves the specified cache entry info.

Arguments:

    lpszUrlName : name of the url file (unused now).

    lpCacheEntryInfo : pointer to the url info structure that receives the url
        info.

    lpdwCacheEntryInfoBufferSize : pointer to a location where length of
        the above buffer is passed in. On return, this contains the length
        of the above buffer that is fulled in.

Return Value:

    Windows Error Code.

--*/
{
    ENTER_CACHE_API ((DBG_API, Bool, "GetUrlCacheEntryInfoA", "%q, %#x, %#x",
        lpszUrlName, lpCacheEntryInfo, lpdwCacheEntryInfoBufferSize));

    DWORD Error;

    // Validate parameters.
    if( IsBadUrl( lpszUrlName ) ||
        (lpCacheEntryInfo && !lpdwCacheEntryInfoBufferSize) ||
        (lpCacheEntryInfo && lpdwCacheEntryInfoBufferSize && IsBadWriteUrlInfo(
                lpCacheEntryInfo,
                *lpdwCacheEntryInfoBufferSize) ) )
    {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->GetUrlInfo(
                        lpszUrlName,
                        lpCacheEntryInfo,
                        lpdwCacheEntryInfoBufferSize,
                        LOOKUP_URL_NOCREATE,
                        0);

    LEAVE_CACHE_API();
}


BOOLAPI GetUrlCacheEntryInfoExA(
    IN LPCSTR       lpszUrl,
    OUT LPINTERNET_CACHE_ENTRY_INFOA lpCEI,
    IN OUT LPDWORD  lpcbCEI,
    OUT LPSTR       lpszOut,
    IN OUT LPDWORD  lpcbOut,
    LPVOID          lpReserved,
    DWORD           dwFlags
)
{
    ENTER_CACHE_API ((DBG_API, Bool, "GetUrlCacheEntryInfoExA",
        "%q, %#x, %#x, %#x, %#x, %#x, %#x", lpszUrl, lpCEI, lpcbCEI, lpszOut, lpcbOut, lpReserved, dwFlags));

    DWORD Error;

    // Validate parameters
    // NOTE: once the following params change, edit GetUrlCacheEntryInfoExW accordingly.
    if (   IsBadUrl(lpszUrl)
        || lpszOut
        || lpcbOut 
        || lpReserved
       )
    {
        INET_ASSERT (FALSE);
        Error = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    // We allow mixing of INTERNET_CACHE_FLAG_ALLOW_COLLISIONS with lookup flags
    Error = GlobalUrlContainers->GetUrlInfo
        (lpszUrl, lpCEI, lpcbCEI, LOOKUP_URL_TRANSLATE | (dwFlags & INTERNET_CACHE_FLAG_ALLOW_COLLISIONS), dwFlags);
        
    LEAVE_CACHE_API();
}


URLCACHEAPI_(BOOL) SetUrlCacheEntryInfoA(
    IN LPCSTR lpszUrlName,
    IN LPCACHE_ENTRY_INFOA lpCacheEntryInfo,
    IN DWORD dwFieldControl
    )
/*++

Routine Description:

    This function sets the specified fields of the cache entry info.

Arguments:

    lpszUrlName : name of the url file (unused now).

    lpCacheEntryInfo : pointer to the url info structure that has the url info to
        be set.

    dwFieldControl : Bitmask that specifies the fields to be set.

Return Value:

    Windows Error Code.

--*/
{
    ENTER_CACHE_API ((DBG_API, Bool, "SetUrlCacheEntryInfoA", "%q, %#x, %d",
        lpszUrlName, lpCacheEntryInfo, dwFieldControl));

    DWORD Error;

    //
    // validate parameters.
    //

    if( IsBadUrl( lpszUrlName ) ||
            IsBadReadUrlInfo( lpCacheEntryInfo )) {

        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->SetUrlInfo(
                        lpszUrlName,
                        lpCacheEntryInfo,
                        dwFieldControl );

    LEAVE_CACHE_API();
}

URLCACHEAPI_(HANDLE) FindFirstUrlCacheEntryA(
    IN LPCSTR lpszUrlSearchPattern,
    OUT LPCACHE_ENTRY_INFOA lpFirstCacheEntryInfo,
    IN OUT LPDWORD lpdwFirstCacheEntryInfoBufferSize
    )
/*++

Routine Description:

    This member function starts the cache entries enumeration and returns
    the first entry in the cache.

Arguments:

    lpszUrlSearchPattern : pointer to a search pattern string. Currently
        it is not implemented.

    lpFirstCacheEntryInfo : pointer to a cache entry info structure.

Return Value:

    Returns the find first handle. If the returned handle is NULL,
    GetLastError() returns the extended error code.

--*/
{
    ENTER_CACHE_API ((DBG_API, Bool, "FindFirstUrlCacheEntryA",
        "%q, %#x, %#x",
        lpszUrlSearchPattern,
        lpFirstCacheEntryInfo,
        lpdwFirstCacheEntryInfoBufferSize
    ));

    DWORD Error;
    HANDLE hFind = 0;

    // Validate parameters.
    if (IsBadWriteUrlInfo(lpFirstCacheEntryInfo,
                          *lpdwFirstCacheEntryInfoBufferSize))
    {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    // Get the first entry.
    Error = GlobalUrlContainers->FindNextEntry(&hFind, 
                                               lpszUrlSearchPattern, 
                                               lpFirstCacheEntryInfo, 
                                               lpdwFirstCacheEntryInfoBufferSize, 
                                               URLCACHE_FIND_DEFAULT_FILTER,
                                               NULL,
                                               FIND_FLAGS_OLD_SEMANTICS);

Cleanup:

    if( Error != ERROR_SUCCESS )
    {
        GlobalUrlContainers->FreeFindHandle(hFind);
        hFind = NULL;
        SetLastError(Error);
        DEBUG_ERROR(API, Error);
    }

    DEBUG_LEAVE_API (hFind);
    return hFind;
}

URLCACHEAPI_(BOOL) FindNextUrlCacheEntryA(
    IN HANDLE hFind,
    OUT LPCACHE_ENTRY_INFOA lpNextCacheEntryInfo,
    IN OUT LPDWORD lpdwNextCacheEntryInfoBufferSize
    )
/*++

Routine Description:

    This member function returns the next entry in the cache.

Arguments:

    hEnumHandle : Find First handle.

    lpFirstCacheEntryInfo : pointer to a cache entry info structure.

Return Value:

    Returns the find first handle. If the returned handle is NULL,
    GetLastError() returns the extended error code. It returns
    ERROR_NO_MORE_ITEMS after it returns the last entry in the cache.

--*/
{
    ENTER_CACHE_API ((DBG_API, Bool, "FindNextUrlCacheEntryA",
        "%#x, %#x, %#x",
        hFind, 
        lpNextCacheEntryInfo,
        lpdwNextCacheEntryInfoBufferSize
    ));

    DWORD Error = ERROR_SUCCESS;
    CACHE_FIND_FIRST_HANDLE* pFind;

    // Validate parameters.
    if (!hFind || IsBadWriteUrlInfo(lpNextCacheEntryInfo,
                                    *lpdwNextCacheEntryInfoBufferSize)) 
    {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }


    // Get the next entry.
    Error = GlobalUrlContainers->FindNextEntry(&hFind, 
                                               NULL, 
                                               lpNextCacheEntryInfo, 
                                               lpdwNextCacheEntryInfoBufferSize,
                                               URLCACHE_FIND_DEFAULT_FILTER,
                                               NULL,
                                               FIND_FLAGS_OLD_SEMANTICS);



Cleanup:
    if (Error!=ERROR_SUCCESS)
    {
        SetLastError(Error);
        DEBUG_ERROR(INET, Error);
    }
    DEBUG_LEAVE_API(Error==ERROR_SUCCESS);
    return (Error == ERROR_SUCCESS );
}


INTERNETAPI_(HANDLE) FindFirstUrlCacheEntryExA(
    IN     LPCSTR    lpszUrlSearchPattern,
    IN     DWORD     dwFlags,
    IN     DWORD     dwFilter,
    IN     GROUPID   GroupId,
    OUT    LPINTERNET_CACHE_ENTRY_INFOA pEntryInfo,
    IN OUT LPDWORD   pcbEntryInfo,
    OUT    LPVOID    lpGroupAttributes,     // must pass NULL
    IN OUT LPDWORD   pcbGroupAttributes,    // must pass NULL
    IN     LPVOID    lpReserved             // must pass NULL
    )
{
    ENTER_CACHE_API ((DBG_API, Bool, "FindFirstUrlCacheEntryExA",
        "%q, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x",
        lpszUrlSearchPattern,
        dwFlags,
        dwFilter,
        GroupId,
        pEntryInfo,
        pcbEntryInfo,
        lpGroupAttributes,
        pcbGroupAttributes,
        lpReserved
    ));

    DWORD Error;
    HANDLE hFind = NULL;

    // Validate parameters.
    if (IsBadWritePtr (pcbEntryInfo, sizeof(DWORD)))
    {
        Error = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    // Get the first entry.
    Error = GlobalUrlContainers->FindNextEntry(&hFind, 
                                               lpszUrlSearchPattern, 
                                               pEntryInfo, 
                                               pcbEntryInfo,
                                               dwFilter,
                                               GroupId,
                                               dwFlags);

Cleanup:

    if( Error != ERROR_SUCCESS )
    {
        if (hFind)
        {
            GlobalUrlContainers->FreeFindHandle(hFind);
            hFind = NULL;
        }
        SetLastError(Error);
        DEBUG_ERROR(API, Error);
    }

    DEBUG_LEAVE_API (hFind);
    return hFind;    
}

BOOLAPI FindNextUrlCacheEntryExA(
    IN     HANDLE    hFind,
    OUT    LPINTERNET_CACHE_ENTRY_INFOA pEntryInfo,
    IN OUT LPDWORD   pcbEntryInfo,
    OUT    LPVOID    lpGroupAttributes,     // must pass NULL
    IN OUT LPDWORD   pcbGroupAttributes,    // must pass NULL
    IN     LPVOID    lpReserved             // must pass NULL
    )
{
    ENTER_CACHE_API ((DBG_API, Bool, "FindNextUrlCacheEntryExA",
        "%#x, %#x, %#x, %#x, %#x, %#x",
        hFind,
        pEntryInfo,
        pcbEntryInfo,
        lpGroupAttributes,
        pcbGroupAttributes,
        lpReserved
    ));

    DWORD Error;

    // Validate parameters.
    if (!hFind || IsBadWritePtr (pcbEntryInfo, sizeof(DWORD)))
    {
        Error = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }
    

    // Get the next entry.
    Error = GlobalUrlContainers->FindNextEntry(&hFind, 
                                               NULL, 
                                               pEntryInfo, 
                                               pcbEntryInfo, 
                                               NULL, 
                                               NULL,
                                               NULL);
    

    LEAVE_CACHE_API();
}

URLCACHEAPI_(BOOL) FreeUrlCacheSpaceA(
    IN LPCSTR lpszCachePath,
    IN DWORD dwFactor,
    IN DWORD dwFilter
    )
/*++

Routine Description:

    This function cleans up the cache entries in the specified ccahe
    path to make space for future cache entries.

Arguments:

    dwFactor: % of free space

Return Value:

    TRUE if the cleanup is successful. Otherwise FALSE, GetLastError()
    returns the extended error.

--*/
{
    DWORD Error;
    
    ENTER_CACHE_API ((DBG_API, Bool, "FreeUrlCacheSpace", 
        "<path>,%d, %#x", dwFactor, dwFilter));

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->CleanupUrls(lpszCachePath, dwFactor, dwFilter);

    LEAVE_CACHE_API();
}

URLCACHEAPI_(BOOL) UnlockUrlCacheEntryFileA(
    LPCSTR lpszUrlName,
    IN DWORD dwReserved
    )
/*++

Routine Description:

    This API checks in the file that was check out as part of
    RetrieveUrlFile API.

Arguments:

    lpszUrlName : name of the URL that is being retrieved.

    dwReserved : reserved for future use.

Return Value:

    Windows Error code.

--*/
{
   
    DWORD Error;
    DWORD i;

    ENTER_CACHE_API ((DBG_API, Bool, "UnlockUrlCacheEntryFile",
        "%q, %#x", lpszUrlName, dwReserved));

    // validate parameters.
    if( IsBadUrl( lpszUrlName )  ) {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->UnlockUrl(lpszUrlName);

    LEAVE_CACHE_API();
}

URLCACHEAPI_(BOOL) DeleteUrlCacheEntryA(
    IN LPCSTR lpszUrlName
    )
{
    ENTER_CACHE_API ((DBG_API, Bool, "DeleteUrlCacheEntry",
        "%q", lpszUrlName));


    DWORD Error;

    // Validate parameters.
    if( IsBadUrl( lpszUrlName ) ) {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }
    
    Error = GlobalUrlContainers->DeleteUrl(lpszUrlName);

    LEAVE_CACHE_API();
}

BOOLAPI SetUrlCacheEntryGroupA(
    IN LPCSTR   lpszUrlName,
    IN DWORD    dwFlags,
    IN GROUPID  GroupId,
    IN LPBYTE   pbGroupAttributes, // must pass NULL
    IN DWORD    cbGroupAttributes, // must pass 0
    IN LPVOID   lpReserved         // must pass NULL
    )
{
    ENTER_CACHE_API ((DBG_API, Bool, "SetUrlCacheEntryGroupA", 
        "%q, %#x, %#x, %#x, %#x, %#x", lpszUrlName, dwFlags, GroupId, pbGroupAttributes, cbGroupAttributes, lpReserved));

    DWORD Error;

    // Validate parameters.
    if (IsBadUrl(lpszUrlName)
        || !GroupId
        || pbGroupAttributes
        || cbGroupAttributes
        || lpReserved
        )
    {
        Error =  ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->SetUrlGroup (lpszUrlName, dwFlags, GroupId);

    LEAVE_CACHE_API();
}



URLCACHEAPI_(BOOL) GetUrlCacheGroupAttributeA(
    IN      GROUPID                         gid,
    IN      DWORD                           dwFlags,
    IN      DWORD                           dwAttributes,
    OUT     LPINTERNET_CACHE_GROUP_INFOA    lpGroupInfo,
    IN OUT  LPDWORD                         lpdwGroupInfo,
    IN OUT  LPVOID                          lpReserved
)
{
    ENTER_CACHE_API ((DBG_API, Bool, "GetUrlCacheGroupAttributeA",
        "%#x, %d, %d, %#x, %#x, %#x", 
        gid, dwFlags, dwAttributes, lpGroupInfo, lpdwGroupInfo, lpReserved ));

    DWORD Error;

    // Validate parameters.
    if( !lpGroupInfo ||
        !lpdwGroupInfo ||
        IsBadWriteUrlInfo(lpGroupInfo, *lpdwGroupInfo) ) 
    {
        Error = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    if( *lpdwGroupInfo < sizeof(INTERNET_CACHE_GROUP_INFOA) )
    {
        Error = ERROR_INSUFFICIENT_BUFFER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->GetGroupAttributes(
                        gid,
                        dwAttributes,
                        lpGroupInfo,
                        lpdwGroupInfo );
    LEAVE_CACHE_API();
}

URLCACHEAPI_(BOOL) SetUrlCacheGroupAttributeA(
    IN      GROUPID                         gid,
    IN      DWORD                           dwFlags,
    IN      DWORD                           dwAttributes,
    IN      LPINTERNET_CACHE_GROUP_INFOA    lpGroupInfo,
    IN OUT  LPVOID                          lpReserved
)
{
    ENTER_CACHE_API ((DBG_API, Bool, "SetUrlCacheGroupAttributeA",
        "%#x, %d, %d, %#x, %#x", 
        gid, dwFlags, dwAttributes, lpGroupInfo, lpReserved));

    DWORD Error;

    // validate parameters.
    if( IsBadReadPtr(lpGroupInfo, sizeof(INTERNET_CACHE_GROUP_INFOA) ) ) 
    {
        Error = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // Initialize globals.
    if (!InitGlobals())
    {
        Error = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    Error = GlobalUrlContainers->SetGroupAttributes(
            gid, dwAttributes, lpGroupInfo);

    LEAVE_CACHE_API();
}

BOOLAPI IsUrlCacheEntryExpiredA(
    IN      LPCSTR       lpszUrlName,
    IN      DWORD        dwFlags,
    IN OUT  FILETIME*    pftLastModifiedTime
)
{
    BOOL                        bRet = TRUE;
    CACHE_ENTRY_INFOEX*         pCEI = NULL;    
    DWORD                       cbCEI;
    DWORD                       dwError;
    BOOL                        bLazy = FALSE;
    BOOL                        fLocked = FALSE;

    // Validate parameters.
    if( IsBadUrl( lpszUrlName ) || !pftLastModifiedTime ) {
        INET_ASSERT(FALSE); 
        return ERROR_INVALID_PARAMETER;
    }

    // set out LastModTime to 0
    pftLastModifiedTime->dwLowDateTime = 0 ;
    pftLastModifiedTime->dwHighDateTime = 0 ;


    if (!InitGlobals())
    {
        INET_ASSERT(FALSE);
        return ERROR_INTERNET_INTERNAL_ERROR;
    }

    //
    // BUGBUG
    // ideally, we should use GlobalUrlContainers->GetUrlInfo()
    // with NO_ALLOCATION and HEADONLY flag for perf.
    // however, there is a flag (lookup flag v.s entry flag) collision 
    // in that code path which prevents this working
    // so we use this anti-perf RetrieveUrl for now until that one
    // gets fixed 
    //                                           --DanpoZ, 98.09.09
    
    // Find the container and search the index.
    dwError = GlobalUrlContainers->RetrieveUrl(
                    lpszUrlName, 
                    (CACHE_ENTRY_INFO**) &pCEI, 
                    &cbCEI, 
                    (dwFlags & INTERNET_FLAG_FWD_BACK)?
                        LOOKUP_URL_TRANSLATE : LOOKUP_URL_NOCREATE,
                    RETRIEVE_WITHOUT_CHECKS | RETRIEVE_WITH_ALLOCATION);

    
    // not found in cache
    if( dwError != ERROR_SUCCESS )
        goto Cleanup;    

    fLocked = TRUE;

    // found in cache, get the last modified time
    *pftLastModifiedTime = pCEI->LastModifiedTime;

    bRet = IsExpired(pCEI, dwFlags, &bLazy);
    if( bRet && bLazy )
    {
        //
        // the entry is not expired, however, we need to post-fetch
        // so we have to return EXPIRED back to trident to force them
        // issue a binding, on the new binding, urlmon-wininet returns
        // the cache content and queue a background update
        // (an alternative would be to ask trident to catch this case
        //  and call background update themself)
        // 
        bRet = FALSE;
    }

Cleanup:
    if( pCEI )
        FREE_MEMORY(pCEI);

    if (fLocked)
        GlobalUrlContainers->UnlockUrl(lpszUrlName);

    return bRet;
}