Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3074 lines
88 KiB

/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
cache.cxx
Abstract:
Contains HTTP cache-related functions. Cache functions pulled out of
read.cxx and send.cxx
Contents:
HTTP_REQUEST_HANDLE_OBJECT::FCanWriteToCache
HTTP_REQUEST_HANDLE_OBJECT::FAddIfModifiedSinceHeader
HTTP_REQUEST_HANDLE_OBJECT::AddHeaderIfEtagFound
HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheRetrieval
HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheWrite
HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePreNetIO
HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePostNetIO
HTTP_REQUEST_HANDLE_OBJECT::ResumePartialDownload
HTTP_REQUEST_HANDLE_OBJECT::AddTimestampsFromCacheToResponseHeaders
HTTP_REQUEST_HANDLE_OBJECT::AddTimeHeader
HTTP_REQUEST_HANDLE_OBJECT::IsPartialResponseCacheable
HTTP_REQUEST_HANDLE_OBJECT::LocalEndCacheWrite
HTTP_REQUEST_HANDLE_OBJECT::GetTimeStampsForCache
(FExcludedMimeType)
(FilterHeaders)
Author:
Richard L Firth (rfirth) 05-Dec-1997
Environment:
Win32 user-mode DLL
Revision History:
05-Dec-1997 rfirth
Created
--*/
#include <wininetp.h>
#include <perfdiag.hxx>
#include "httpp.h"
//
// private prototypes
//
PRIVATE
BOOL
FExcludedMimeType(
IN LPSTR lpszMimeType,
IN DWORD dwMimeTypeSize
);
PRIVATE
VOID
FilterHeaders(
IN LPSTR lpszHeaderInfo,
OUT LPDWORD lpdwHeaderLen
);
//
// static data
//
LPCSTR rgszExcludedMimeTypes[] = {
"multipart/mixed",
"multipart/x-mixed-replace"
};
const DWORD rgdwExcludedMimeTypeSizes[] = {
sizeof("multipart/mixed") - 1,
sizeof("multipart/x-mixed-replace") - 1
};
static const char szDefaultExtension[] = "txt";
LPSTR rgszExcludeHeaders[] = {
HTTP_SET_COOKIE_SZ,
HTTP_LAST_MODIFIED_SZ,
HTTP_SERVER_SZ,
HTTP_DATE_SZ,
HTTP_EXPIRES_SZ,
HTTP_CONNECTION_SZ,
HTTP_PROXY_CONNECTION_SZ,
HTTP_VIA_SZ,
HTTP_VARY_SZ,
HTTP_AGE_SZ,
HTTP_CACHE_CONTROL_SZ,
HTTP_ACCEPT_RANGES_SZ,
HTTP_CONTENT_DISPOSITION_SZ
};
const char vszUserNameHeader[4] = "~U:";
//
// HTTP Request Handle Object methods
//
BOOL
HTTP_REQUEST_HANDLE_OBJECT::FCanWriteToCache(
VOID
)
/*++
Routine Description:
Determines if we can write this file to the cache
Arguments:
None.
Return Value:
BOOL
--*/
{
DEBUG_ENTER((DBG_HTTP,
Bool,
"HTTP_REQUEST_HANDLE_OBJECT::FCanWriteToCache",
NULL
));
PERF_LOG(PE_CACHE_WRITE_CHECK_START);
BOOL ok = FALSE;
BOOL fForceToCache = FALSE;
BOOL fCheckNeedFile = FALSE;
BOOL fVary = FALSE;
BOOL fContentEnc = FALSE;
//
// Set fNoCache if there is pragma: no-cache
//
BOOL fNoCache = FALSE;
DWORD length, index;
LPSTR lpszBuf;
_ResponseHeaders.LockHeaders();
if (GetOpenFlags() & INTERNET_FLAG_SECURE)
{
SetPerUserItem(TRUE);
//
// Determine if there are any Pragma: no-cache headers.
//
index = 0;
while ( FastQueryResponseHeader(HTTP_QUERY_PRAGMA,
(LPVOID *) &lpszBuf,
&length,
index) == ERROR_SUCCESS )
{
if (length == NO_CACHE_LEN &&
strnicmp(NO_CACHE_SZ, lpszBuf, NO_CACHE_LEN) == 0)
{
fNoCache = TRUE;
break;
}
index++;
}
if (fNoCache)
{
if(GlobalBypassSSLNoCacheCheck)
{
goto check_need_file;
}
// If server disabled caching over SSL, don't even create
// a file, let alone commit it to the cache. Game over.
BETA_LOG (DOWNLOAD_NO_FILE);
goto quit;
}
//
// If we've disabled caching for SSL servers, consider creating
// a download file if the client insists upon it.
//
if (GlobalDisableSslCaching)
{
goto check_need_file;
}
}
//
// Also set fNoCache if there is Cache-Control: no-cache or no-store header,
// if there is a Cache-Control: private header and we're *not* on NT with user profiles,
// or any Vary: headers. These are only checked for HTTP 1.1 servers.
//
if (IsResponseHttp1_1())
{
CHAR *ptr, *pToken;
index = 0;
// Scan for Cache-Control header.
while (FastQueryResponseHeader(HTTP_QUERY_CACHE_CONTROL,
(LPVOID *) &lpszBuf,
&length,
index) == ERROR_SUCCESS)
{
// Check for no-cache or no-store or private.
CHAR chTemp = lpszBuf[length];
lpszBuf[length] = '\0';
pToken = ptr = lpszBuf;
// Parse a token from the string; test for sub headers.
while (*pToken != '\0')
{
SKIPWS(pToken);
// no-cache, no-store.
if (strnicmp(NO_CACHE_SZ, pToken, NO_CACHE_LEN) == 0)
{
fNoCache = TRUE;
break;
}
if( strnicmp(NO_STORE_SZ, pToken, NO_STORE_LEN) == 0)
{
fNoCache = TRUE;
fCheckNeedFile = TRUE;
}
// private.
if (strnicmp(PRIVATE_SZ, pToken, PRIVATE_LEN) == 0)
{
SetPerUserItem(TRUE);
}
while (*pToken != '\0')
{
if ( *pToken == ',')
{
pToken++;
break;
}
pToken++;
}
} // while (*pToken != '\0')
//
// We've finished parsing it, now return our terminator back to its proper place
//
lpszBuf[length] = chTemp;
// If fNoCache, we're done. Break out of switch.
if (fNoCache)
break;
index++;
} // while FastQueryResponseHeader == ERROR_SUCCESS
if (fNoCache)
{
if( fCheckNeedFile )
{
// cache-control: no store
goto check_need_file;
}
else
if ((GetOpenFlags() & INTERNET_FLAG_SECURE) && !GlobalBypassSSLNoCacheCheck)
{
// If server disabled caching over SSL, don't even create
// a file, let alone commit it to the cache. Game over.
BETA_LOG (DOWNLOAD_NO_FILE);
goto quit;
}
else
{
//
// This is not SSL/PCT, so don't cache but consider creating a
// download file if one was requested.
//
goto check_need_file;
}
}
// Finally, check if any Vary: headers exist, EXCEPT "Vary: User-Agent"
index = 0;
if (FastQueryResponseHeader(HTTP_QUERY_VARY,
(LPVOID *) &lpszBuf,
&length,
index) == ERROR_SUCCESS
&& !(length == USER_AGENT_LEN
&& !strnicmp (lpszBuf, USER_AGENT_SZ, length)) )
{
// content-encoding?
if (FastQueryResponseHeader(HTTP_QUERY_CONTENT_ENCODING,
(LPVOID *) &lpszBuf,
&length,
index) == ERROR_SUCCESS )
{
fContentEnc = TRUE;
}
fVary = TRUE;
goto check_need_file;
}
}
if (GetCacheFlags() & INTERNET_FLAG_NO_CACHE_WRITE)
{
goto check_need_file;
}
//
// accept HTTP/1.0 or downlevel server responses
//
if ((GetStatusCode() == HTTP_STATUS_OK) || (GetStatusCode() == 0))
{
if (FastQueryResponseHeader(HTTP_QUERY_CONTENT_TYPE, (LPVOID *) &lpszBuf, &length, 0) == ERROR_SUCCESS)
{
if (FExcludedMimeType(lpszBuf, length))
{
DEBUG_PRINT(CACHE,
INFO,
("%s Mime Excluded from caching\n",
lpszBuf
));
goto check_need_file;
}
}
//
// BUGBUG should we also check for size and keep an upper bound?
//
ok = TRUE;
goto quit;
}
else
{
INTERNET_SCHEME schemeType = GetSchemeType();
if ((schemeType == INTERNET_SCHEME_FTP)
|| (schemeType == INTERNET_SCHEME_GOPHER))
{
ok = TRUE;
goto quit;
}
}
check_need_file:
INET_ASSERT (!ok);
if (GetCacheFlags() & INTERNET_FLAG_NEED_FILE
|| GetCacheFlags () & INTERNET_FLAG_HYPERLINK
|| (GlobalBypassSSLNoCacheCheck && fNoCache))
{
//
// Create a download file but don't commit it to the cache.
//
BETA_LOG (DOWNLOAD_FILE_NEEDED);
fForceToCache = !ok;
ok = TRUE;
if( !fContentEnc )
{
// if content-enc, the client will write to cache...
// so we should not delete the data file
SetDeleteDataFile();
}
DEBUG_PRINT(CACHE, INFO, ("don't commit file\n"));
}
else
{
BETA_LOG (DOWNLOAD_FILE_NOT_NEEDED);
}
quit:
if ((!ok || fForceToCache) && !fVary)
{
SetCacheWriteDisabled();
// Deleting originally cached files if (& only if) the server sends cache-control headers.
// Bugs 98644 and 101578
if (fNoCache)
{
_RequestHeaders.LockHeaders();
// In 5.0, Rosebud worked like this:
// 1. A call to CommitUrlCacheEntry with a file size of about 11 bytes.
// 2. A call to HttpSendRequest.
// 3. A call to DeleteUrlCacheEntry that succeeds.
// 4. Another call to CommitUrlCacheEntry for the same file, except it's grown.
// 5. Repeat 3 and 4 until file is downloaded.
// However, we made a change so that if the server sends "no-cache", we'll
// delete any cache entry during the call in (2). This caused Rosebud to break
// We don't want to go back to the original behaviour, since that would introduce
// IE 94486. So we look for a Translate: F header to guide us.
DWORD dwIndex = 0;
BOOL fFound = FALSE;
TCHAR szValue[MAX_PATH];
DWORD ccValue = ARRAY_ELEMENTS(szValue);
while (ERROR_SUCCESS==QueryRequestHeader(
"Translate",
ARRAY_ELEMENTS("Translate")-1,
(LPVOID)szValue,
&ccValue,
0,
&dwIndex))
{
if (!StrCmpI(szValue, "F"))
{
fFound = TRUE;
break;
}
ccValue = ARRAY_ELEMENTS(szValue);
}
_RequestHeaders.UnlockHeaders();
if (!fFound)
{
DeleteUrlCacheEntry(GetOriginalUrl());
}
}
}
_ResponseHeaders.UnlockHeaders();
PERF_LOG(PE_CACHE_WRITE_CHECK_END);
DEBUG_LEAVE(ok);
return ok;
}
BOOL
HTTP_REQUEST_HANDLE_OBJECT::FAddIfModifiedSinceHeader(
IN LPCACHE_ENTRY_INFO lpCEI
)
/*++
Routine Description:
description-of-function.
Arguments:
lpCEI -
Return Value:
BOOL
--*/
{
DEBUG_ENTER((DBG_HTTP,
Bool,
"HTTP_REQUEST_HANDLE_OBJECT::FAddIfModifiedSinceHeader",
"%#x",
lpCEI
));
PERF_ENTER(FAddIfModifiedSinceHeader);
char buff[64], buffh[256];
DWORD dwLen, error;
BOOL success = FALSE;
INET_ASSERT (FT2LL(lpCEI->LastModifiedTime));
dwLen = sizeof(buff);
if (FFileTimetoHttpDateTime(&(lpCEI->LastModifiedTime), buff, &dwLen))
{
LPSTR pszBuf;
if (lpCEI->CacheEntryType & HTTP_1_1_CACHE_ENTRY)
{
INET_ASSERT (dwLen);
pszBuf = buff;
}
else
{
dwLen = wsprintf(buffh, "%s; length=%d", buff, lpCEI->dwSizeLow);
pszBuf = buffh;
}
DEBUG_PRINT(CACHE,
INFO,
("%s %s - empty\n",
GlobalKnownHeaders[HTTP_QUERY_IF_MODIFIED_SINCE].Text,
buffh
));
error = ReplaceRequestHeader(HTTP_QUERY_IF_MODIFIED_SINCE,
pszBuf,
dwLen,
0, // no index
ADD_HEADER_IF_NEW
);
if ((error == ERROR_SUCCESS) || (error == ERROR_HTTP_HEADER_ALREADY_EXISTS))
{
if (error == ERROR_SUCCESS)
{
// we added the header on behalf of the app. This is equivalent
// to the app having specified INTERNET_FLAG_RESYNCHRONIZE
SetAutoSync();
}
success = TRUE;
}
}
PERF_LEAVE(FAddIfModifiedSinceHeader);
DEBUG_LEAVE(success);
return success;
}
BOOL
HTTP_REQUEST_HANDLE_OBJECT::AddHeaderIfEtagFound(
IN LPCACHE_ENTRY_INFO lpCEI
)
/*++
Routine Description:
Adds if-modified-since header if an etag was present in the cache entry headers.
Arguments:
lpCEI -
Return Value:
BOOL
--*/
{
if (!(lpCEI->CacheEntryType & HTTP_1_1_CACHE_ENTRY))
return TRUE;
// BUGBUG - always parsing, use flag.
CHAR buf[512];
DWORD nIndex = 0, cbBuf = 512, dwError = ERROR_SUCCESS;
HTTP_HEADER_PARSER hp((CHAR*) lpCEI->lpHeaderInfo, lpCEI->dwHeaderInfoSize);
if (hp.FindHeader((CHAR*) lpCEI->lpHeaderInfo, HTTP_QUERY_ETAG,
0, buf, &cbBuf, &nIndex) == ERROR_SUCCESS)
{
DWORD dwQueryIndex;
if (lpCEI->CacheEntryType & SPARSE_CACHE_ENTRY)
{
dwQueryIndex = HTTP_QUERY_IF_RANGE;
}
else
{
dwQueryIndex = HTTP_QUERY_IF_NONE_MATCH;
}
dwError = ReplaceRequestHeader(dwQueryIndex,
buf,
cbBuf,
0,
ADD_HEADER_IF_NEW
);
}
if ((dwError == ERROR_SUCCESS) || (dwError == ERROR_HTTP_HEADER_ALREADY_EXISTS))
return TRUE;
return FALSE;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheRetrieval(
IN BOOL bReset,
IN BOOL bOffline,
IN BOOL bNoRetrieveIfExist
)
/*++
Routine Description:
Starts retrieving data for this object from the cache
Arguments:
bReset - if TRUE, forcefully reset the (keep-alive) connection
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_NOT_ENOUGH_MEMORY
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheRetrieval",
"%B, %B",
bReset,
bOffline
));
PERF_LOG(PE_CACHE_RETRIEVE_START);
DWORD error;
LPSTR lpHeaders = NULL;
if (bOffline)
{
if (!IsOffline() && IsCacheReadDisabled())
{
error = ERROR_FILE_NOT_FOUND;
goto quit;
}
if( !bNoRetrieveIfExist )
error = UrlCacheRetrieve (TRUE);
else
error = ERROR_SUCCESS;
if (error != ERROR_SUCCESS)
goto quit2;
}
INET_ASSERT(_hCacheStream && _pCacheEntryInfo);
//
// RecordCacheRetrieval() will set end-of-file if it succeeds
//
error = RecordCacheRetrieval (_pCacheEntryInfo);
if (error != ERROR_SUCCESS)
{
UrlCacheUnlock();
goto quit2;
}
if (bOffline && lstrcmp(_pCacheEntryInfo->lpszSourceUrlName, GetCacheKey()))
{
// Simulate a redirect to the client.
InternetIndicateStatusString
(INTERNET_STATUS_REDIRECT, _pCacheEntryInfo->lpszSourceUrlName);
// The cache translated through a redirect.
FreeSecondaryCacheKey (); // POST redirects must be to GET
SetURL (_pCacheEntryInfo->lpszSourceUrlName);
}
if (bOffline &&
(_pCacheEntryInfo->CacheEntryType & MUST_REVALIDATE_CACHE_ENTRY))
{
INET_ASSERT (_pCacheEntryInfo->CacheEntryType & HTTP_1_1_CACHE_ENTRY);
// Offline mode. Check for a Cache-Control: must-revalidate header.
// If so, allow cache data to be retrieved only if not expired.
FILETIME ftCurrentTime;
GetCurrentGmtTime(&ftCurrentTime);
LONGLONG qwExpire = FT2LL(_pCacheEntryInfo->ExpireTime);
LONGLONG qwCurrent = FT2LL(ftCurrentTime);
if (qwCurrent > qwExpire)
{
error = ERROR_FILE_NOT_FOUND;
goto quit;
}
else
{
goto check_if_modified_since;
}
}
check_if_modified_since:
if (!IsOffline() && IsCacheReadDisabled())
{
//
// We are in offline mode. (Really should assert this.)
// Allow cache data to be retrieved only if last-modified time is
// STRICTLY greater than if-modified-since time added by client.
//
LONGLONG qwLastModified = FT2LL(_pCacheEntryInfo->LastModifiedTime);
LONGLONG qwIfModifiedSince;
SYSTEMTIME stIfModifiedSince;
DWORD length = sizeof(stIfModifiedSince);
DWORD index = 0;
_RequestHeaders.LockHeaders();
error = QueryRequestHeader(HTTP_QUERY_IF_MODIFIED_SINCE,
&stIfModifiedSince,
&length,
HTTP_QUERY_FLAG_SYSTEMTIME,
&index
);
_RequestHeaders.UnlockHeaders();
if (error != ERROR_SUCCESS)
{
// It's theoretically possible the client was redirected,
// removed i-m-s on redirect callback, then went offline.
error = ERROR_FILE_NOT_FOUND;
goto quit;
}
if (!SystemTimeToFileTime(&stIfModifiedSince,
(FILETIME*)&qwIfModifiedSince))
{
error = ERROR_FILE_NOT_FOUND;
goto quit;
}
if (qwLastModified <= qwIfModifiedSince)
{
error = ERROR_FILE_NOT_FOUND;
goto quit;
}
}
// allocate buffer for headers
lpHeaders = (LPSTR)ALLOCATE_FIXED_MEMORY(_pCacheEntryInfo->dwHeaderInfoSize);
if (!lpHeaders)
{
error = ERROR_NOT_ENOUGH_MEMORY;
goto quit;
}
memcpy(lpHeaders, _pCacheEntryInfo->lpHeaderInfo, _pCacheEntryInfo->dwHeaderInfoSize);
// we are hijacking the HTTP request object. If there is already
// a pending network operation (we started a network transaction
// but realised that we have the same data in cache (i.e. the
// data has not expired since we last wrote it to cache)) then
// we need to kill it
if (bReset)
{
// if we have a keep-alive connection AND the server returned
// 304 then the parameter can be FALSE so that we don't kill
// the connection, just release it
//
// ResetObject() will reset end-of-file if it succeeds
//
// We DO NOT clear out the request headers so that the app can still
// query them if necessary
//dprintf(">>> resetting %s\n", GetURL());
error = ResetObject(!(IsKeepAlive() && (GetBytesInSocket() == 0)), FALSE);
if (error != ERROR_SUCCESS)
goto quit;
}
error = CreateResponseHeaders(&lpHeaders, _pCacheEntryInfo->dwHeaderInfoSize);
if (error != ERROR_SUCCESS)
goto quit;
error = AddTimestampsFromCacheToResponseHeaders(_pCacheEntryInfo);
if (error != ERROR_SUCCESS)
goto quit;
/* Send notification about past cookie-events on this URL */
InternetCookieHistory history;
DWORD dwEntryType = _pCacheEntryInfo->CacheEntryType;
history.fAccepted = (dwEntryType & COOKIE_ACCEPTED_CACHE_ENTRY);
history.fLeashed = (dwEntryType & COOKIE_LEASHED_CACHE_ENTRY);
history.fDowngraded = (dwEntryType & COOKIE_DOWNGRADED_CACHE_ENTRY);
history.fRejected = (dwEntryType & COOKIE_REJECTED_CACHE_ENTRY);
if (history.fAccepted ||
history.fLeashed ||
history.fDowngraded ||
history.fRejected)
InternetIndicateStatus (INTERNET_STATUS_COOKIE_HISTORY,
(void*) &history,
sizeof(history));
/* Reevaluate P3P policy to generate policy notifications */
ExtractSetCookieHeaders(NULL);
// we have to set end-of-file again: RecordCacheRetrieval() set
// it, then we wiped it out in ResetObject()
// FYI: We set end-of-file because we have all the data in the
// stream available locally; it doesn't mean that our virtual
// stream pointer is positioned at the end of the file
DEBUG_PRINT(CACHE, INFO, ("Found in the cache\n"));
SetState(HttpRequestStateObjectData);
SetFromCache();
SetEndOfFile();
SetHaveContentLength(TRUE);
_VirtualCacheFileSize =
_RealCacheFileSize =
_BytesRemaining =
_ContentLength = _pCacheEntryInfo->dwSizeLow;
INET_ASSERT (error == ERROR_SUCCESS);
quit:
if (error != ERROR_SUCCESS)
EndCacheRetrieval();
if (lpHeaders)
FREE_MEMORY(lpHeaders);
quit2:
PERF_LOG(PE_CACHE_RETRIEVE_END);
DEBUG_LEAVE(error);
return error;
}
BOOL
HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheWrite(
VOID
)
/*++
Routine Description:
Preps the cache for writing. Sets up the cache filename with an extension
based on the MIME type and optionally writes the headers to the cache data
stream
Arguments:
None.
Return Value:
BOOL
TRUE - Success
FALSE - Failure
--*/
{
DEBUG_ENTER((DBG_HTTP,
Bool,
"HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheWrite",
NULL
));
PERF_ENTER(FHttpBeginCacheWrite);
INET_ASSERT(!IsCacheReadInProgress());
INET_ASSERT(!IsCacheWriteInProgress());
PERF_TRACE(FHttpBeginCacheWrite, 1);
_ResponseHeaders.LockHeaders();
LPSTR lpszFileExtension = NULL;
BOOL fIsUncertainMime = FALSE;
char cExt[DEFAULT_MAX_EXTENSION_LENGTH + 1];
char buf[256];
DWORD dwLen, cbFileName, dwIndex;
CHAR *ptr, *pToken, *pszFileName = NULL;
CHAR szFileName[MAX_PATH];
LPSTR lpszBuf;
// Create a temp local cache file.
// If we find a Content-Disposition header, parse out any filename and use it.
if (QueryResponseHeader (HTTP_QUERY_CONTENT_DISPOSITION,
buf, &(dwLen = sizeof(buf)), 0, &(dwIndex = 0) ) == ERROR_SUCCESS)
{
// Could have multiple tokens in it. Scan for the "filename" token.
ptr = pToken = buf;
while (ptr = StrTokEx2(&pToken, ";"))
{
// Skip any leading ws in token.
SKIPWS(ptr);
// Compare against "filename".
if (!strnicmp(ptr, FILENAME_SZ, FILENAME_LEN))
{
// Found it.
ptr += FILENAME_LEN;
// Skip ws before '='.
SKIPWS(ptr);
// Must have '='
if (*ptr == '=')
{
// Skip any ws after '=' and point
// to beginning of the file name
ptr++;
SKIPWS(ptr);
// Skip past any quotes
if (*ptr == '\"')
ptr++;
SKIPWS(ptr);
cbFileName = strlen(ptr);
if (cbFileName)
{
// Ignore any trailing quote.
if (ptr[cbFileName-1] == '\"')
cbFileName--;
memcpy(szFileName, ptr, cbFileName);
szFileName[cbFileName] = '\0';
pszFileName = szFileName;
}
}
break;
}
}
}
// Either no Content-disposition header or filename not parsed.
if (!pszFileName)
{
DWORD dwMimeLen;
if ((FastQueryResponseHeader(HTTP_QUERY_CONTENT_ENCODING, (LPVOID *) &lpszBuf, &dwMimeLen, 0) == ERROR_SUCCESS) &&
StrCmpNI(lpszBuf,"binary",6) )
{
// if there is content encoding, we should not use
// content-type for file extension
//Modifying this for bug 98611.
//For 'binary' encoding use the Content-Type to find extension
lpszFileExtension = NULL;
}
else if (FastQueryResponseHeader(HTTP_QUERY_CONTENT_TYPE, (LPVOID *) &lpszBuf, &dwMimeLen, 0) == ERROR_SUCCESS)
{
dwLen = sizeof(cExt);
fIsUncertainMime = strnicmp(lpszBuf, "text/plain", dwMimeLen)==0;
PERF_TRACE(FHttpBeginCacheWrite, 2);
if (!fIsUncertainMime &&
GetFileExtensionFromMimeType(lpszBuf, dwMimeLen, cExt, &dwLen))
{
//
// get past the '.' because the cache expects it that way
//
lpszFileExtension = &cExt[1];
}
}
//
// if we couldn't get the MIME type or failed to map it then try to get
// the file extension from the object name requested
//
if (lpszFileExtension == NULL)
{
dwLen = sizeof(cExt);
PERF_TRACE(FHttpBeginCacheWrite, 3);
lpszFileExtension = GetFileExtensionFromUrl(GetURL(), &dwLen);
if (lpszFileExtension != NULL)
{
memcpy(cExt, lpszFileExtension, dwLen);
cExt[dwLen] = '\0';
lpszFileExtension = cExt;
}
PERF_TRACE(FHttpBeginCacheWrite, 4);
}
if ((lpszFileExtension == NULL) && fIsUncertainMime)
{
INET_ASSERT(sizeof(szDefaultExtension) < DEFAULT_MAX_EXTENSION_LENGTH);
strcpy(cExt, szDefaultExtension);
lpszFileExtension = cExt;
}
}
//
// BUGBUG - BeginCacheWrite() wants the estimated file size, but we just
// give it 0 for now
//
//dwError = pRequest->BeginCacheWrite(dwLen, lpszFileExtension);
PERF_TRACE(FHttpBeginCacheWrite, 5);
DWORD dwError = BeginCacheWrite(0, lpszFileExtension, pszFileName);
PERF_TRACE(FHttpBeginCacheWrite, 6);
_ResponseHeaders.UnlockHeaders();
PERF_LEAVE(FHttpBeginCacheWrite);
BOOL success = (dwError == ERROR_SUCCESS);
DEBUG_LEAVE(success);
return success;
}
/*============================================================================
IsExpired (...)
4/17/00 (RajeevD) Corrected back arrow behavior and wrote detailed comment.
We have a cache entry for the URL. This routine determines whether we should
synchronize, i.e. do an if-modified-since request. This answer depends on 3
factors: navigation mode, expiry on cache entry if any, and syncmode setting.
1. There are two navigation modes:
a. hyperlinking - clicking on a link, typing a URL, starting browser etc.
b. back/forward - using the back or forward buttons in the browser.
In b/f case we generally want to display what was previously shown. Ideally
wininet would cache multiple versions of a given URL and trident would specify
which one to use when hitting back arrow. For now, the best we can do is use
the latest (only) cache entry or resync with the server.
EXCEPTION: if the cache entry sets http/1.1 cache-control: must-revalidate,
we treat as if we were always hyperlinking to the cache entry. This is
normally used during offline mode to suppress using a cache entry after
expiry. This overloaded usage gives sites a workaround if they dislike our
new back button behavior.
2. Expiry may fall into one of 3 buckets:
a. no expiry information
b. expiry in past of current time (hyperlink) or last-access time (back/fwd)
c. expiry in future of current time (hyperlink) or-last access time (back/fwd)
3. Syncmode may have 3 settings
a. always - err on side of freshest data at expense of net perf.
b. never - err on side of best net perf at expense of stale data.
c. once-per-session - middle-of-the-road setting
d. automatic - slight variation of once-per-session where we decay frequency
of i-m-s for images that appear to be static. This is the default.
Based on these factors, there are 5 possible result values in matrices below:
1 synchronize
0 don't synchronize
? synchronize if last-sync time was before start of the current session,
?- Like per-session except if URL is marked static and has a delay interval.
0+ Don't sync if URL is marked static, else fall back to per-session
HYPERLINKING
When hyperlinking, expiry takes precedence, then we look at syncmode.
No Expiry Expiry in Future Expiry in Past
Syncmode of Current Time of Current Time
Always 1 0 1
Never 0 0 1
Per-Session ? 0 1
Automatic ?- 0 1
BACK/FORWARD
When going back or forward, we generally don't sync. The exception is if
we should have sync'ed the URL on the previous navigate but didn't. We
deduce this by looking at the last-sync time of the entry.
No Expiry Expiry in Future Expiry in Past
Syncmode of Last-Access Time of Last-Access Time
Always ? 0 ?
Never 0 0 ?
Per-Session ? 0 ?
Automatic 0+ 0 ?
When considering what might have happened when hyperlinking to this URL,
the decision tree has 5 outcomes:
1. We might have had no cache entry and downloaded to cache for the first time
2. Else we might have had a cache entry and used it w/o i-m-s
3. Else we did i-m-s but the download was aborted
4. Or the i-m-s returned not modified
5. Or the i-m-s returned new content
Only in case 3 do we want to resync the cache entry.
============================================================================*/
BOOL IsExpired (
CACHE_ENTRY_INFOEX* pInfo,
DWORD dwCacheFlags,
BOOL* pfLazyUpdate )
{
BOOL fExpired;
FILETIME ftCurrentTime;
GetCurrentGmtTime (&ftCurrentTime);
if ((dwCacheFlags & INTERNET_FLAG_FWD_BACK)
&& !(pInfo->CacheEntryType & MUST_REVALIDATE_CACHE_ENTRY))
{
// BACK/FORWARD CASE
if (FT2LL (pInfo->ExpireTime) != LONGLONG_ZERO)
{
// We have an expires time.
if (FT2LL (pInfo->ExpireTime) > FT2LL(pInfo->LastAccessTime))
{
// Expiry was in future of last access time, so don't resync.
fExpired = FALSE;
}
else
{
// Entry was originally expired. Make sure it was sync'ed once.
fExpired = (FT2LL(pInfo->LastSyncTime) < dwdwSessionStartTime);
}
}
else switch (GlobalUrlCacheSyncMode)
{
default:
case WININET_SYNC_MODE_AUTOMATIC:
if (pInfo->CacheEntryType & STATIC_CACHE_ENTRY)
{
fExpired = FALSE;
break;
}
// else intentional fall-through...
case WININET_SYNC_MODE_ALWAYS:
case WININET_SYNC_MODE_ONCE_PER_SESSION:
fExpired = (FT2LL(pInfo->LastSyncTime) < dwdwSessionStartTime);
break;
case WININET_SYNC_MODE_NEVER:
fExpired = FALSE;
break;
} // end switch
}
else
{
// HYPERLINKING CASE
// Always strictly honor expire time from the server.
INET_ASSERT(pfLazyUpdate);
*pfLazyUpdate = FALSE;
if( (pInfo->CacheEntryType & POST_CHECK_CACHE_ENTRY ) &&
!(dwCacheFlags & INTERNET_FLAG_BGUPDATE) )
{
//
// this is the (instlled) post check cache entry, so we will do
// post check on this ietm
//
fExpired = FALSE;
*pfLazyUpdate = TRUE;
}
else if (FT2LL(pInfo->ExpireTime) != LONGLONG_ZERO)
{
// do we have postCheck time?
//
// ftPostCheck ftExpire
// | |
// --------------|----------------------------|-----------> time
// | |
// not expired | not expired (bg update) | expired
//
//
LONGLONG qwPostCheck = FT2LL(pInfo->ftPostCheck);
if( qwPostCheck != LONGLONG_ZERO )
{
LONGLONG qwCurrent = FT2LL(ftCurrentTime);
if( qwCurrent < qwPostCheck )
{
fExpired = FALSE;
}
else
if( qwCurrent < FT2LL(pInfo->ExpireTime) )
{
fExpired = FALSE;
// set background update flag
// (only if we are not doing lazy updating ourselfs)
if ( !(dwCacheFlags & INTERNET_FLAG_BGUPDATE) )
{
*pfLazyUpdate = TRUE;
}
}
else
{
fExpired = TRUE;
}
}
else
fExpired = FT2LL(pInfo->ExpireTime) <= FT2LL(ftCurrentTime);
}
else switch (GlobalUrlCacheSyncMode)
{
case WININET_SYNC_MODE_NEVER:
// Never check, unless the page has expired
fExpired = FALSE;
break;
case WININET_SYNC_MODE_ALWAYS:
fExpired = TRUE;
break;
default:
case WININET_SYNC_MODE_AUTOMATIC:
if (pInfo->CacheEntryType & STATIC_CACHE_ENTRY)
{
// We believe this entry never actually changes.
// Check the entry if interval since last checked
// is less than 25% of the time we had it cached.
LONGLONG qwTimeSinceLastCheck = FT2LL (ftCurrentTime)
- FT2LL(pInfo->LastSyncTime);
LONGLONG qwTimeSinceDownload = FT2LL (ftCurrentTime)
- FT2LL (pInfo->ftDownload);
fExpired = qwTimeSinceLastCheck > qwTimeSinceDownload/4;
break;
}
// else intentional fall through to once-per-session rules.
case WININET_SYNC_MODE_ONCE_PER_SESSION:
fExpired = TRUE;
// Huh. We don't have an expires, so we'll improvise
// but wait! if we are hyperlinking then there is added
// complication. This semantic has been figured out
// on Netscape after studying various sites
// if the server didn't send us expiry time or lastmodifiedtime
// then this entry expires when hyperlinking
// this happens on queries
if (dwCacheFlags & INTERNET_FLAG_HYPERLINK
&& !FT2LL(pInfo->LastModifiedTime))
{
// shouldn't need the hyperlink test anymore
DEBUG_PRINT(UTIL, INFO, ("Hyperlink semantics\n"));
INET_ASSERT(fExpired==TRUE);
break;
}
// We'll assume the data could change within a day of the last time
// we sync'ed.
// We want to refresh UNLESS we've seen the page this session
// AND the session's upper bound hasn't been exceeded.
if ((dwdwSessionStartTime < FT2LL(pInfo->LastSyncTime))
&&
(FT2LL(ftCurrentTime) < FT2LL(pInfo->LastSyncTime) +
dwdwHttpDefaultExpiryDelta))
{
fExpired = FALSE;
}
break;
} // end switch
} // end else for hyperlinking case
#ifdef DBG
char buff[64];
PrintFileTimeInInternetFormat(&ftCurrentTime, buff, sizeof(buff));
DEBUG_PRINT(UTIL, INFO, ("Current Time: %s\n", buff));
PrintFileTimeInInternetFormat(&(pInfo->ExpireTime), buff, sizeof(buff));
DEBUG_PRINT(UTIL, INFO, ("Expiry Time: %s\n", buff));
PrintFileTimeInInternetFormat(&(pInfo->ftPostCheck), buff, sizeof(buff));
DEBUG_PRINT(UTIL, INFO, ("PostCheck Time: %s\n", buff));
PrintFileTimeInInternetFormat(&(pInfo->LastSyncTime), buff, sizeof(buff));
DEBUG_PRINT(UTIL, INFO, ("Last Sync Time: %s\n", buff));
PrintFileTimeInInternetFormat(&(pInfo->ftDownload), buff, sizeof(buff));
DEBUG_PRINT(UTIL, INFO, ("Last Download Time: %s\n", buff));
PrintFileTimeInInternetFormat((FILETIME *)&dwdwSessionStartTime, buff, sizeof(buff));
DEBUG_PRINT(UTIL, INFO, ("Session start Time: %s\n", buff));
DEBUG_PRINT(UTIL, INFO, ("CacheFlags=%x\n", dwCacheFlags));
DEBUG_PRINT(HTTP, INFO,
("CheckExpired: Url %s Expired \n", (fExpired ? "" : "Not")));
#endif //DBG
return fExpired;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePreNetIO(
VOID
)
/*++
Routine Description:
Check if in the cache. If so, check for expired, if expired do if-modified
-since, else get from the cache.
Arguments:
None.
Return Value:
DWORD Windows Error Code
ERROR_SUCCESS: started retrieval from the cache
ERROR_FILE_NOT_FOUND: go to the wire
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePreNetIO",
NULL
));
PERF_LOG(PE_CACHE_EXPIRY_CHECK_START);
DWORD error = ERROR_FILE_NOT_FOUND;
if (_pCacheEntryInfo || IsCacheReadDisabled())
{
INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
goto done;
}
PERF_LOG(PE_TRACE, 0x91);
//
// See if we have a cache entry and if so lock it.
//
// Check for the cdrom insertion case Set error to
// ERROR_INTERNET_INSERT_CDROM ONLY only in this
// case, since this will be handled. Otherwise,
// error defaults to ERROR_FILE_NOT_FOUND.
DWORD dwError;
dwError = UrlCacheRetrieve(FALSE);
if (dwError != ERROR_SUCCESS)
{
if (dwError == ERROR_INTERNET_INSERT_CDROM)
error = dwError;
else
BETA_LOG (CACHE_MISS);
goto done;
}
if (_pCacheEntryInfo->CacheEntryType & SPARSE_CACHE_ENTRY)
{
//
// Open the file so it can't be deleted.
//
_CacheFileHandle =
CreateFile
(
_pCacheEntryInfo->lpszLocalFileName,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
//
// Check the file size.
//
if (_CacheFileHandle == INVALID_HANDLE_VALUE
|| (_pCacheEntryInfo->dwSizeLow !=
SetFilePointer (_CacheFileHandle, 0, NULL, FILE_END)))
{
FREE_MEMORY (_pCacheEntryInfo);
_pCacheEntryInfo = NULL;
INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
goto done;
}
// We have a partial cache entry for this URL. No need to check
// for expiry or correct user. Add a Range: header to get the
// rest of the data.
char szBuf[64];
DWORD cbBuf =
wsprintf (szBuf, "bytes=%d-", _pCacheEntryInfo->dwSizeLow);
if (ERROR_SUCCESS != ReplaceRequestHeader
(HTTP_QUERY_RANGE, szBuf, cbBuf, 0, ADD_HEADER))
{
goto done;
}
//
// If there was Last-Modified-Time, add Unless-Modified-Since header,
// which assures coherency in the event the URL data changed since
// the partial download.
//
if (FT2LL(_pCacheEntryInfo->LastModifiedTime) != LONGLONG_ZERO)
{
cbBuf = sizeof(szBuf);
FFileTimetoHttpDateTime(&(_pCacheEntryInfo->LastModifiedTime),
szBuf, &cbBuf);
ReplaceRequestHeader (
HTTP_QUERY_UNLESS_MODIFIED_SINCE,
szBuf, cbBuf, 0, ADD_HEADER
);
}
//
// Similarly, if the entry has a 1.1 ETag, add the If-Range header.
//
AddHeaderIfEtagFound(_pCacheEntryInfo);
}
else
{
PERF_LOG(PE_TRACE, 0x93);
BOOL fIsExpired = IsExpired
(_pCacheEntryInfo, GetCacheFlags(), &_fLazyUpdate);
PERF_LOG(PE_TRACE, 0x94);
//
// found the cache entry
// if it is expired, or we are asked to do if-modified-since
// then we do it. However, if the entry is an Installed entry,
// we skip netio unless the entry is also marked EDITED_CACHE_ENTRY
// and the client has specifically asked for
// INTERNET_OPTION_BYPASS_EDITED_ENTRY.
if ((!(_pCacheEntryInfo->CacheEntryType & INSTALLED_CACHE_ENTRY)
|| ((_pCacheEntryInfo->CacheEntryType & EDITED_CACHE_ENTRY)
&& GlobalBypassEditedEntry))
&& (fIsExpired || (GetCacheFlags() & INTERNET_FLAG_RESYNCHRONIZE)
))
{
//
// add if-modified-since only if there is lastmodifiedtime
// sent back by the site. This way you never get into trouble
// where the sitedoesn't send you an lastmodtime and you
// send if-modified-since based on a clock which might be ahead
// of the site. So the site might say nothing is modified even though
// something might be. www.microsoft.com is one such example
//
if (FT2LL(_pCacheEntryInfo->LastModifiedTime) != LONGLONG_ZERO)
{
DEBUG_PRINT(HTTP,
INFO,
("%s expired, doing IF-MODIFIED-SINCE\n",
GetURL()
));
PERF_LOG(PE_TRACE, 0x97);
FAddIfModifiedSinceHeader(_pCacheEntryInfo);
PERF_LOG(PE_TRACE, 0x98);
}
// If this is an HTTP 1.1 then check to see if an Etag:
// header is present and add an If-None-Match header.
AddHeaderIfEtagFound(_pCacheEntryInfo);
INET_ASSERT(error == ERROR_FILE_NOT_FOUND);
}
else
{
//
// The cache entry is not expired, so we don't hit the net.
//
BETA_LOG (CACHE_NOT_EXPIRED);
PERF_LOG(PE_TRACE, 0x99);
ReuseObject();
error = FHttpBeginCacheRetrieval(FALSE, FALSE);
INET_ASSERT (error == ERROR_SUCCESS);
}
}
done:
PERF_LOG(PE_CACHE_EXPIRY_CHECK_END);
DEBUG_LEAVE(error);
return (error);
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePostNetIO(
IN DWORD dwStatusCode,
IN BOOL fVariation
)
/*++
Routine Description:
Check if this response needs to be cached or pulled from the cache
after we have received the response from the net. Here we check for
if-modified-since.
Arguments:
dwStatusCode - HTTP status code
Return Value:
DWORD Windows Error Code
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePostNetIO",
"%d",
dwStatusCode
));
DWORD error = ERROR_FILE_NOT_FOUND;
BOOL fLastModTimeFromServer = FALSE;
BOOL fExpireTimeFromServer = FALSE;
BOOL fPostCheckTimeFromServer = FALSE;
//
// check if
// we can actually read the data from the cache after all. The idea here is
// to improve efficiency by returning data from the cache if it hasn't
// changed at the server. We determine this by the server returning a 304
// response, or by determining that the date on the response is less than or
// equal to the expires timestamp on the data we already wrote to cache
//
INET_ASSERT((dwStatusCode == HTTP_STATUS_NOT_MODIFIED)
|| (dwStatusCode == HTTP_STATUS_PRECOND_FAILED)
|| (dwStatusCode == HTTP_STATUS_OK)
|| (dwStatusCode == HTTP_STATUS_PARTIAL_CONTENT)
|| (dwStatusCode == 0));
GetTimeStampsForCache(&_ftExpires,
&_ftLastModified,
&_ftPostCheck,
&fExpireTimeFromServer,
&fLastModTimeFromServer,
&fPostCheckTimeFromServer
);
if (IsCacheReadDisabled())
{
INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
goto quit;
}
//
// we may have no cache entry info, so no point in continuing - this file
// was not in the cache when we started. (We could try to retrieve again
// in case another download has put it in the cache)
//
if (_pCacheEntryInfo == NULL)
{
if (fVariation)
{
error = UrlCacheRetrieve(TRUE);
}
if (error!=ERROR_SUCCESS)
{
goto quit;
}
}
if (_pCacheEntryInfo->CacheEntryType & SPARSE_CACHE_ENTRY)
{
if (dwStatusCode == HTTP_STATUS_PARTIAL_CONTENT)
{
BETA_LOG (CACHE_RESUMED);
error = ResumePartialDownload();
}
else
{
BETA_LOG (CACHE_NOT_RESUMED);
INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
}
goto quit;
}
//
// we are optimizing further here
// if the return status is OK and the server sent us a
// last modified time and this time is the same as
// what we got earlier then we use the entry from the cache
//
if ((dwStatusCode == HTTP_STATUS_NOT_MODIFIED)
|| ((fLastModTimeFromServer)
&& (FT2LL(_ftLastModified)
== FT2LL(_pCacheEntryInfo->LastModifiedTime))
&& GetMethodType() == HTTP_METHOD_TYPE_GET))
{
BETA_LOG (CACHE_NOT_MODIFIED);
DEBUG_PRINT(HTTP,
INFO,
("response code=%d %s, using cache entry\n",
GetStatusCode(),
GetURL()
));
DWORD dwAction = CACHE_ENTRY_SYNCTIME_FC;
GetCurrentGmtTime(&(_pCacheEntryInfo->LastSyncTime));
if (fExpireTimeFromServer)
{
(_pCacheEntryInfo->ExpireTime).dwLowDateTime = _ftExpires.dwLowDateTime;
(_pCacheEntryInfo->ExpireTime).dwHighDateTime = _ftExpires.dwHighDateTime;
dwAction |= CACHE_ENTRY_EXPTIME_FC;
}
// update the cache entry type if needed
DWORD dwType;
dwType = GetCacheEntryType();
if (dwType)
_pCacheEntryInfo->CacheEntryType |= dwType;
dwAction |= CACHE_ENTRY_TYPE_FC;
//
// Update the last sync time to the current time
// so we can do once_per_session logic
//
if (!SetUrlCacheEntryInfoA(_pCacheEntryInfo->lpszSourceUrlName, _pCacheEntryInfo, dwAction))
{
// NB if this call fails, the worst that could happen is
// that next time around we will do an if-modified-since
// again
INET_ASSERT(FALSE);
}
error = FHttpBeginCacheRetrieval(TRUE, FALSE);
if (error != ERROR_SUCCESS)
{
//
// if we failed to abort the transaction, or to retrieve the
// data from the cache, then return the error only if the
// response if not OK. Otherwise we can continue and
// get the data
//
if (dwStatusCode != HTTP_STATUS_OK)
{
// this should never happen to us
INET_ASSERT(FALSE);
error = ERROR_FILE_NOT_FOUND;
}
}
}
else // We could not use the cache entry so release it.
{
BETA_LOG (CACHE_MODIFIED);
INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
}
quit:
if (error != ERROR_SUCCESS)
UrlCacheUnlock();
DEBUG_LEAVE(error);
return error;
}
DWORD HTTP_REQUEST_HANDLE_OBJECT::ResumePartialDownload(void)
/*++
Routine Description:
description-of-function.
Arguments:
None.
Return Value:
DWORD
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::ResumePartialDownload",
NULL));
_ResponseHeaders.LockHeaders();
const static char sz200[] = "200";
INET_ASSERT (!_CacheWriteInProgress);
LPSTR pszHeader;
DWORD cbHeader;
// Retrieve the start and end of Content-Range header
if (!_iSlotContentRange)
goto quit;
pszHeader = _ResponseHeaders.GetHeaderPointer
(_ResponseBuffer, _iSlotContentRange);
INET_ASSERT (pszHeader);
PSTR pszLimit;
pszLimit = StrChr (pszHeader, '\n');
INET_ASSERT (pszLimit);
*pszLimit = 0;
// Extract the document length as a string and number.
// The http 1.1 spec is very explicit that we MUST parse
// the header and return an error if invalid. We expect
// it to be of the form Content-Range: bytes xxx-yyy/zzz.
PSTR pszStart, pszEnd, pszLength;
DWORD dwStart, dwEnd, dwLength;
// Ensure that value is prefixed with "bytes"
PSTR pszBytes;
pszBytes = pszHeader + (GlobalKnownHeaders[HTTP_QUERY_CONTENT_RANGE].Length+1); // +1 for the ':'
SKIPWS (pszBytes);
if (strnicmp (pszBytes, BYTES_SZ, BYTES_LEN))
goto quit;
// Parse and validate start of range.
pszStart = pszBytes + BYTES_LEN;
SKIPWS (pszStart);
dwStart = StrToInt (pszStart);
if (dwStart != _pCacheEntryInfo->dwSizeLow)
goto quit;
// Parse and validate end of range.
pszEnd = StrChr (pszStart, '-');
if (!pszEnd++)
goto quit;
dwEnd = StrToInt (pszEnd);
if (dwStart > dwEnd)
goto quit;
// Parse and validate length.
pszLength = StrChr (pszEnd, '/');
if (!pszLength)
goto quit;
pszLength++;
dwLength = StrToInt (pszLength);
if (dwEnd + 1 != dwLength)
goto quit;
// The Content-Length header is the amount transmitted, however
// as far as the client is concerned only the total amount of
// data matters, so we fix it up from the Content-Range header.
// Use the Content-Range buffer for the new Content-Length.
// We know the buffer is big enough because the last number
// in the Content-Range header is the new Content-Length value.
_ContentLength = dwLength;
// Remove the old Content-Length header, if any.
if (_iSlotContentLength)
{
_ResponseHeaders.RemoveAllByIndex(HTTP_QUERY_CONTENT_LENGTH);
//_ResponseHeaders.RemoveHeader (_iSlotContentLength, HTTP_QUERY_CONTENT_LENGTH, &_bKnownHeaders[dwQueryIndex]);
}
cbHeader = wsprintf (pszHeader, "%s: %d", GlobalKnownHeaders[HTTP_QUERY_CONTENT_LENGTH].Text,
dwLength);
_ResponseHeaders.ShrinkHeader
(_ResponseBuffer, _iSlotContentRange, HTTP_QUERY_CONTENT_RANGE, HTTP_QUERY_CONTENT_LENGTH, cbHeader);
// Fix up the response headers to appear same as a 200 response.
// Revise the status line from 206 to 200. The status code
// starts after the first space, e.g. "HTTP/1.0 206 Partial Content"
pszHeader = _ResponseHeaders.GetHeaderPointer (_ResponseBuffer, 0);
INET_ASSERT (pszHeader);
LPSTR pszStatus;
pszStatus = StrChr (pszHeader, ' ');
SKIPWS (pszStatus);
INET_ASSERT (!memcmp(pszStatus, "206", 3));
memcpy (pszStatus, sz200, sizeof(sz200));
_ResponseHeaders.ShrinkHeader (_ResponseBuffer, 0,
HTTP_QUERY_STATUS_TEXT, HTTP_QUERY_STATUS_TEXT,
(DWORD) (pszStatus - pszHeader) + sizeof(sz200) - 1);
_StatusCode = HTTP_STATUS_OK;
// Some servers omit "Accept-Ranges: bytes" since it is implicit
// in a 206 response. This is important to Adobe Amber ActiveX
// control and other clients that may issue their own range
// requests. Add the header if not present.
if (!IsResponseHeaderPresent(HTTP_QUERY_ACCEPT_RANGES))
{
const static char szAccept[] = "Accept-Ranges: bytes";
DWORD dwErr = AddInternalResponseHeader
(HTTP_QUERY_ACCEPT_RANGES, (LPSTR) szAccept, CSTRLEN(szAccept));
INET_ASSERT (dwErr == ERROR_SUCCESS);
}
// Adjust the handle values for socket read and file write positions.
if (IsKeepAlive())
_BytesRemaining = _ContentLength;
_VirtualCacheFileSize = dwStart;
_RealCacheFileSize = dwStart;
SetAvailableDataLength (dwStart);
// Save the cache file path.
INET_ASSERT(!_CacheFileName);
_CacheFileName = NewString(_pCacheEntryInfo->lpszLocalFileName);
if (!_CacheFileName)
goto quit;
// Open the file for read (should already be open for write)
INET_ASSERT (_CacheFileHandle != INVALID_HANDLE_VALUE);
_CacheFileHandleRead = CreateFile (_CacheFileName, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (_CacheFileHandleRead == INVALID_HANDLE_VALUE)
goto quit;
// We can discard the partial cache entry info.
FREE_MEMORY (_pCacheEntryInfo);
_pCacheEntryInfo = NULL;
_CacheWriteInProgress = TRUE;
quit:
_ResponseHeaders.UnlockHeaders();
DWORD dwErr;
if (_CacheWriteInProgress)
{
dwErr = ERROR_SUCCESS;
}
else
{
INET_ASSERT (FALSE);
dwErr = ERROR_HTTP_INVALID_SERVER_RESPONSE;
if (_CacheFileName)
{
FREE_MEMORY (_CacheFileName);
_CacheFileName = NULL;
}
if (_CacheFileHandleRead != INVALID_HANDLE_VALUE)
{
CloseHandle (_CacheFileHandleRead);
_CacheFileHandleRead = INVALID_HANDLE_VALUE;
}
}
DEBUG_LEAVE (dwErr);
return dwErr;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::AddTimestampsFromCacheToResponseHeaders(
IN LPCACHE_ENTRY_INFO lpCacheEntryInfo
)
/*++
Routine Description:
description-of-function.
Arguments:
lpCacheEntryInfo -
Return Value:
DWORD
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::AddTimestampsFromCacheToResponseHeaders",
"%#x",
lpCacheEntryInfo
));
PERF_LOG(PE_TRACE, 0x701);
_ResponseHeaders.LockHeaders();
DWORD error = AddTimeHeader(lpCacheEntryInfo->ExpireTime,
HTTP_QUERY_EXPIRES
);
if (error == ERROR_SUCCESS) {
error = AddTimeHeader(lpCacheEntryInfo->LastModifiedTime,
HTTP_QUERY_LAST_MODIFIED
);
}
if (error == ERROR_INVALID_PARAMETER) {
error = ERROR_SUCCESS;
}
_ResponseHeaders.UnlockHeaders();
PERF_LOG(PE_TRACE, 0x702);
DEBUG_LEAVE(error);
return error;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::AddTimeHeader(
IN FILETIME fTime,
IN DWORD dwHeaderIndex
)
/*++
Routine Description:
Adds a time header to this object
Arguments:
fTime - FILETIME value to add as header value
dwHeaderIndex - contains an index into a global array of Header strings.
NOTE: Must be called under Header Critical Section!!!
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_INVALID_PARAMETER
Couldn't convert fTime
--*/
{
DEBUG_ENTER((DBG_HTTP,
None,
"HTTP_REQUEST_HANDLE_OBJECT::AddTimeHeader",
"%#x:%#x, %u [%q]",
fTime.dwLowDateTime,
fTime.dwHighDateTime,
dwHeaderIndex,
GlobalKnownHeaders[dwHeaderIndex].Text
));
char buf[MAX_PATH];
SYSTEMTIME systemTime;
DWORD error = ERROR_SUCCESS;
if (FT2LL(fTime) != LONGLONG_ZERO) {
if (FileTimeToSystemTime((CONST FILETIME *)&fTime, &systemTime)) {
memcpy(buf, GlobalKnownHeaders[dwHeaderIndex].Text, GlobalKnownHeaders[dwHeaderIndex].Length);
buf[GlobalKnownHeaders[dwHeaderIndex].Length] = ':';
buf[GlobalKnownHeaders[dwHeaderIndex].Length+1] = ' ';
if (InternetTimeFromSystemTime((CONST SYSTEMTIME *)&systemTime,
INTERNET_RFC1123_FORMAT,
buf + (GlobalKnownHeaders[dwHeaderIndex].Length+2),
sizeof(buf) - (GlobalKnownHeaders[dwHeaderIndex].Length+2)
)) {
error = AddInternalResponseHeader(dwHeaderIndex, buf, lstrlen(buf));
INET_ASSERT(error == ERROR_SUCCESS);
//
// if it came upto here and all went well, only then would
// the error be set to ERROR_SUCCESS
//
DEBUG_PRINT(HTTP,
INFO,
("Cache: Adding header %s, errorcode=%d\n",
buf,
error
));
} else {
INET_ASSERT(FALSE);
error = ERROR_INVALID_PARAMETER;
}
} else {
INET_ASSERT(FALSE);
error = ERROR_INVALID_PARAMETER;
}
}
DEBUG_LEAVE(error);
return error;
}
BOOL HTTP_REQUEST_HANDLE_OBJECT::IsPartialResponseCacheable(void)
/*++
Routine Description:
description-of-function.
Arguments:
None.
Return Value:
BOOL
--*/
{
LPSTR lpszHeader;
DWORD cbHeader;
DWORD dwIndex;
BOOL fRet = FALSE;
DWORD err;
_ResponseHeaders.LockHeaders();
if (GlobalDisableReadRange || GetMethodType() != HTTP_METHOD_TYPE_GET)
{
INET_ASSERT(fRet == FALSE);
goto quit;
}
if (_RealCacheFileSize >= _ContentLength)
{
// We don't handle chunked transfer upon resuming a partial
// download, so we require a Content-Length header. Also,
// if download file is actually complete, yet not committed
// to cache (possibly because the client didn't read to eof)
// then we don't want to save a partial download. Otherwise
// we might later start a range request for the file starting
// one byte beyond eof. MS Proxy 1.0 will return an invalid
// 206 response containing the last byte of the file. Other
// servers or proxies that follow the latest http spec might
// return a 416 response which is equally useless to us.
INET_ASSERT(fRet == FALSE);
goto quit;
}
// For HTTP/1.0, must have last-modified time, otherwise we
// don't have a way to tell if the partial downloads are coherent.
if (!IsResponseHttp1_1() && (FT2LL(_ftLastModified) == 0))
{
INET_ASSERT(fRet == FALSE);
goto quit;
}
if (!_iSlotContentRange)
{
// We didn't get a Content-Range header which implies the server
// supports byte range for this URL, so we must look for the
// explicit invitation of "Accept-Ranges: bytes" response header.
dwIndex = 0;
err = FastQueryResponseHeader (HTTP_QUERY_ACCEPT_RANGES,
(LPVOID *) &lpszHeader,&cbHeader, dwIndex);
if (err != ERROR_SUCCESS || !(cbHeader == BYTES_LEN && !strnicmp(lpszHeader, BYTES_SZ, cbHeader)) )
{
INET_ASSERT(fRet == FALSE);
goto quit;
}
}
if (!IsResponseHttp1_1())
{
// For HTTP/1.0, only cache responses from Server: Microsoft-???/*
// Microsoft-PWS-95/*.* will respond with a single range but
// with incorrect Content-Length and Content-Range headers.
// Other 1.0 servers may return single range in multipart response.
const static char szPrefix[] = "Microsoft-";
const static DWORD ibSlashOffset = sizeof(szPrefix)-1 + 3;
dwIndex = 0;
if ( ERROR_SUCCESS != FastQueryResponseHeader (HTTP_QUERY_SERVER,
(LPVOID *) &lpszHeader, &cbHeader, dwIndex)
|| cbHeader <= ibSlashOffset
|| lpszHeader[ibSlashOffset] != '/'
|| memcmp (lpszHeader, szPrefix, sizeof(szPrefix) - 1)
)
{
INET_ASSERT(fRet == FALSE);
goto quit;
}
}
else // if (IsResponseHttp1_1())
{
// For http 1.1, must have strong etag. A weak etag starts with
// a character other than a quote mark and cannot be used as a
// coherency validator. IIS returns a weak etag when content is
// modified within a single file system time quantum.
if (ERROR_SUCCESS != FastQueryResponseHeader (HTTP_QUERY_ETAG,
(LPVOID *) &lpszHeader, &cbHeader, 0)
|| *lpszHeader != '\"')
{
INET_ASSERT(fRet == FALSE);
goto quit;
}
}
fRet = TRUE;
quit:
_ResponseHeaders.UnlockHeaders();
return fRet;
}
DWORD
HTTP_REQUEST_HANDLE_OBJECT::LocalEndCacheWrite(
IN BOOL fNormal
)
/*++
Routine Description:
Finishes up the cache-write operation, either committing the cache entry or
deleting it (because we failed)
Arguments:
fNormal - TRUE if normal end-cache-write operation (i.e. FALSE if error)
Return Value:
DWORD
Success - ERROR_SUCCESS
Failure - ERROR_NOT_ENOUGH_MEMORY
ERROR_INTERNET_INTERNAL_ERROR
--*/
{
DEBUG_ENTER((DBG_HTTP,
Dword,
"HTTP_REQUEST_HANDLE_OBJECT::LocalEndCacheWrite",
"%B",
fNormal
));
PERF_LOG(PE_TRACE, 0x2001);
LPSTR lpszBuf = NULL;
char buff[256];
DWORD dwBuffLen;
DWORD dwError;
DWORD dwUserNameHeader = 0;
LPSTR lpszHeaderInfo = NULL;
DWORD dwEntryType = GetCacheEntryType();
if (!fNormal)
{
if (!IsPartialResponseCacheable())
{
BETA_LOG (DOWNLOAD_ABORTED);
}
else
{
//
// Flush any buffered data to disk.
// BUGBUG: doesn't work with chunked transfer
// WriteResponseBufferToCache();
// WriteQueryBufferToCache();
//
// dprintf ("wininet: partial cached %s\n", GetURL());
BETA_LOG (DOWNLOAD_PARTIAL);
fNormal = TRUE;
dwEntryType = SPARSE_CACHE_ENTRY;
//
// Disable InternetUnlockRequest
//
LOCK_REQUEST_INFO* pLock =
(LOCK_REQUEST_INFO*) GetLockRequestHandle();
if (pLock)
pLock->fNoDelete = TRUE;
}
}
if (fNormal)
{
if (GetCacheFlags() & INTERNET_FLAG_MAKE_PERSISTENT)
dwEntryType |= STICKY_CACHE_ENTRY;
if (GetSecondaryCacheKey())
dwEntryType |= POST_RESPONSE_CACHE_ENTRY;
if (IsResponseHttp1_1())
{
dwEntryType |= HTTP_1_1_CACHE_ENTRY;
if (IsMustRevalidate())
dwEntryType |= MUST_REVALIDATE_CACHE_ENTRY;
}
if (IsPerUserItem())
{
//
// create per-user header, e.g. "~U:JoeBlow\r\n", if it fits in the
// buffer
//
INET_ASSERT(vdwCurrentUserLen);
dwUserNameHeader = sizeof(vszUserNameHeader) - 1
+ vdwCurrentUserLen
+ sizeof("\r\n");
if (sizeof(buff) >= dwUserNameHeader)
{
memcpy(buff, vszUserNameHeader, sizeof(vszUserNameHeader) - 1);
DWORD dwSize = lstrlen(vszCurrentUser);
memcpy(&buff[sizeof(vszUserNameHeader) - 1],
vszCurrentUser,
dwSize);
dwSize += sizeof(vszUserNameHeader) - 1;
memcpy(&buff[dwSize], "\r\n", sizeof("\r\n"));
}
else
{
// if it failed, mark it as expired
dwUserNameHeader = 0;
GetCurrentGmtTime(&_ftExpires);
AddLongLongToFT(&_ftExpires, (-1)*(ONE_HOUR_DELTA));
}
}
//
// start off with 512 byte buffer (used to be 256, but headers are
// getting larger)
//
dwBuffLen = 512;
do
{
DWORD dwPreviousLength;
lpszBuf = (LPSTR)ResizeBuffer(lpszBuf, dwBuffLen, FALSE);
if (lpszBuf == NULL)
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
break;
}
dwBuffLen -= dwUserNameHeader;
dwPreviousLength = dwBuffLen;
lpszHeaderInfo = lpszBuf;
lpszBuf[0] = '\0';
dwError = QueryRawResponseHeaders(TRUE,
(LPVOID)lpszHeaderInfo,
&dwBuffLen
);
if (dwError == ERROR_SUCCESS)
{
FilterHeaders(lpszHeaderInfo, &dwBuffLen);
if (dwUserNameHeader)
{
lstrcat(lpszHeaderInfo, buff);
dwBuffLen += dwUserNameHeader;
}
break; // get out
}
//
// we have the right error and we haven't allocated more memory yet
//
if (dwError == ERROR_INSUFFICIENT_BUFFER)
{
if (dwBuffLen <= dwPreviousLength)
{
dwError = ERROR_INTERNET_INTERNAL_ERROR;
break;
}
dwBuffLen += dwUserNameHeader;
}
else
{
//
// if HttpQueryInfo() returned ERROR_HTTP_DOWNLEVEL_SERVER
// or we are making CERN proxy requests for FTP or gopher, then
// there are no headers from the origin server, but the
// operation succeeded
//
if (dwError == ERROR_HTTP_DOWNLEVEL_SERVER)
dwError = ERROR_SUCCESS;
//
// either allready tried once with allocation or
// we got some error other than ERROR_INSUFFICIENT_BUFFER
//
INET_ASSERT(*lpszHeaderInfo == '\0');
lpszHeaderInfo = NULL;
dwBuffLen = 0;
break;
}
} while (TRUE);
fNormal = (dwError == ERROR_SUCCESS);
}
if (!fNormal)
{
dwEntryType = 0xffffffff;
lpszHeaderInfo = NULL;
}
//
// Simulate a redirect if the original object was the root
// ("" or "/") and we did not otherwise get a redirect.
//
if (IsObjectRoot() && _OriginalUrl && !lstrcmpi (_OriginalUrl, GetURL()))
{
DWORD dwLen = lstrlen (_OriginalUrl);
INET_ASSERT (_OriginalUrl[dwLen - 1] == '/');
_OriginalUrl[dwLen - 1] = 0;
}
//
// Let the cache know if the item is likely to be a static image.
// 1. No expire time.
// 2. Has last-modified time.
// 3. The content-type is image/*
// 4. No '?' in the URL.
//
LPSTR lpszHeader;
DWORD cbHeader;
BOOL fImage =
( !FT2LL(_ftExpires)
&& FT2LL(_ftLastModified)
&& (ERROR_SUCCESS == FastQueryResponseHeader (HTTP_QUERY_CONTENT_TYPE,
(LPVOID *) &lpszHeader, &cbHeader, 0))
&& (StrCmpNI (lpszHeader, "image/", sizeof("image/")-1) == 0)
&& (!StrChr (GetURL(), '?'))
);
DEBUG_PRINT(CACHE,
INFO,
("Cache write EntryType = %x\r\n",
dwEntryType
));
dwError = EndCacheWrite(&_ftExpires,
&_ftLastModified,
&_ftPostCheck,
dwEntryType,
dwBuffLen,
lpszHeaderInfo,
NULL,
fImage
);
if (fNormal && !(dwEntryType & SPARSE_CACHE_ENTRY))
{
if (dwError == ERROR_SUCCESS)
{
BETA_LOG (DOWNLOAD_CACHED);
}
else
{
BETA_LOG (DOWNLOAD_NOT_CACHED);
}
}
if (lpszBuf != NULL) {
lpszBuf = (LPSTR)FREE_MEMORY(lpszBuf);
INET_ASSERT(lpszBuf == NULL);
}
PERF_LOG(PE_TRACE, 0x2002);
DEBUG_LEAVE(dwError);
return dwError;
}
VOID
HTTP_REQUEST_HANDLE_OBJECT::GetTimeStampsForCache(
OUT LPFILETIME lpftExpiryTime,
OUT LPFILETIME lpftLastModTime,
OUT LPFILETIME lpftPostCheckTime,
OUT LPBOOL lpfHasExpiry,
OUT LPBOOL lpfHasLastModTime,
OUT LPBOOL lpfHasPostCheck
)
/*++
Routine Description:
extracts timestamps from the http response. If the timestamps don't exist,
does the default thing. has additional goodies like checking for expiry etc.
Arguments:
lpftExpiryTime - returned expiry time
lpftLatsModTime - returned last-modified time
lpfHasExpiry - returned TRUE if the response header contains an expiry
timestamp. This patameter can be NULL
lpfHasLastModTime - returned TRUE if the response header contains a
last-modified timestamp. This patameter can be NULL
Return Value:
None.
Comment:
--*/
{
DEBUG_ENTER((DBG_HTTP,
None,
"HTTP_REQUEST_HANDLE_OBJECT::GetTimeStampsForCache",
"%#x, %#x, %#x, %#x",
lpftExpiryTime,
lpftLastModTime,
lpfHasExpiry,
lpfHasLastModTime
));
PERF_LOG(PE_TRACE, 0x9001);
char buf[512];
LPSTR lpszBuf;
BOOL fRet;
DWORD length, index = 0;
BOOL fPostCheck = FALSE;
BOOL fPreCheck = FALSE;
FILETIME ftPreCheckTime;
FILETIME ftPostCheckTime;
fRet = FALSE;
_ResponseHeaders.LockHeaders();
// Determine if a Cache-Control: max-age header exists. If so, calculate expires
// time from current time + max-age minus any delta indicated by Age:
//
// we really want all the post-fetch stuff works with 1.0 proxy
// so we loose our grip a little bit here: enable all Cache-Control
// max-age work with 1.0 response.
//
//if (IsResponseHttp1_1())
{
CHAR *ptr, *pToken;
INT nDeltaSecsPostCheck = 0;
INT nDeltaSecsPreCheck = 0;
while (1)
{
// Scan headers for Cache-Control: max-age header.
length = sizeof(buf);
switch (QueryResponseHeader(HTTP_QUERY_CACHE_CONTROL,
buf,
&length,
0,
&index))
{
case ERROR_SUCCESS:
buf[length] = '\0';
pToken = ptr = buf;
// Parse a token from the string; test for sub headers.
while (pToken = StrTokEx(&ptr, ","))
{
SKIPWS(pToken);
if (strnicmp(POSTCHECK_SZ, pToken, POSTCHECK_LEN) == 0)
{
pToken += POSTCHECK_LEN;
SKIPWS(pToken);
if (*pToken != '=')
break;
pToken++;
SKIPWS(pToken);
nDeltaSecsPostCheck = atoi(pToken);
// Calculate post fetch time
GetCurrentGmtTime(&ftPostCheckTime);
AddLongLongToFT(&ftPostCheckTime, (nDeltaSecsPostCheck * (LONGLONG) 10000000));
fPostCheck = TRUE;
}
else if (strnicmp(PRECHECK_SZ, pToken, PRECHECK_LEN) == 0)
{
// found
pToken += PRECHECK_LEN;
SKIPWS(pToken);
if (*pToken != '=')
break;
pToken++;
SKIPWS(pToken);
nDeltaSecsPreCheck = atoi(pToken);
// Calculate pre fetch time (overwrites ftExpire )
GetCurrentGmtTime(&ftPreCheckTime);
AddLongLongToFT(&ftPreCheckTime, (nDeltaSecsPreCheck * (LONGLONG) 10000000));
fPreCheck = TRUE;
}
else if (strnicmp(MAX_AGE_SZ, pToken, MAX_AGE_LEN) == 0)
{
// Found max-age. Convert to integer form.
// Parse out time in seconds, text and convert.
pToken += MAX_AGE_LEN;
SKIPWS(pToken);
if (*pToken != '=')
break;
pToken++;
SKIPWS(pToken);
INT nDeltaSecs = atoi(pToken);
INT nAge;
// See if an Age: header exists.
// Using a local index variable:
DWORD indexAge = 0;
length = sizeof(INT)+1;
if (QueryResponseHeader(HTTP_QUERY_AGE,
&nAge,
&length,
HTTP_QUERY_FLAG_NUMBER,
&indexAge) == ERROR_SUCCESS)
{
// Found Age header. Convert and subtact from max-age.
// If less or = 0, attempt to get expires header.
nAge = ((nAge < 0) ? 0 : nAge);
nDeltaSecs -= nAge;
if (nDeltaSecs <= 0)
// The server (or some caching intermediary) possibly sent an incorrectly
// calculated header. Use "Expires", if no "max-age" directives at higher indexes.
// Note: This behaviour could cause a situation where the "pre-check"
// and "post-check" are picked up from the current index, and "max-age" is
// picked up from a higher index. "pre-check" and "post-check" are IE 5.x
// extensions, and generally not bunched together with "max-age", so this
// should work fine. More info on "pre-check" and "post-check":
// <http://msdn.microsoft.com/workshop/author/perf/perftips.asp#Use_Cache-Control_Extensions>
continue;
}
// Calculate expires time from max age.
GetCurrentGmtTime(lpftExpiryTime);
//*((LONGLONG *)lpftExpiryTime) += (nDeltaSecs * (LONGLONG) 10000000);
AddLongLongToFT(lpftExpiryTime, (nDeltaSecs * (LONGLONG) 10000000));
fRet = TRUE;
}
else if (strnicmp(MUST_REVALIDATE_SZ, pToken, MUST_REVALIDATE_LEN) == 0)
{
pToken += MUST_REVALIDATE_LEN;
SKIPWS(pToken);
if (*pToken == 0 || *pToken == ',')
SetMustRevalidate();
}
}
// If an expires time has been found, break switch.
if (fRet)
break;
// Need to bump up index to prevent possibility of never-ending outer while(1) loop.
// Otherwise, on exit from inner while, we could be stuck here reading the
// Cache-Control at the same index.
// QueryResponseHeader(HTTP_QUERY_CACHE_CONTROL, ...) will return either the next index,
// or an error, and we'll be good to go:
index++;
continue;
case ERROR_INSUFFICIENT_BUFFER:
index++;
continue;
default:
break; // no more Cache-Control headers.
}
//
// pre-post fetch headers must come in pair, also
// pre fetch header overwrites the expire
// and make sure postcheck < precheck
//
if( fPreCheck && fPostCheck &&
( nDeltaSecsPostCheck < nDeltaSecsPreCheck ) )
{
fRet = TRUE;
*lpftPostCheckTime = ftPostCheckTime;
*lpftExpiryTime = ftPreCheckTime;
if( lpfHasPostCheck )
*lpfHasPostCheck = TRUE;
if( nDeltaSecsPostCheck == 0 &&
!(GetCacheFlags() & INTERNET_FLAG_BGUPDATE) )
{
//
// "post-check = 0"
// this page has already passed the lazy update time
// this means server wants us to do background update
// after the first download
//
// (bg fsm will be created at the end of the cache write)
//
_fLazyUpdate = TRUE;
}
}
else
{
fPreCheck = FALSE;
fPostCheck = FALSE;
}
break; // no more Cache-Control headers.
}
} // Is http 1.1
// If no expires time is calculated from max-age, check for expires header.
if (!fRet)
{
length = sizeof(buf) - 1;
index = 0;
if (QueryResponseHeader(HTTP_QUERY_EXPIRES, buf, &length, 0, &index) == ERROR_SUCCESS)
{
fRet = FParseHttpDate(lpftExpiryTime, buf);
//
// as per HTTP spec, if the expiry time is incorrect, then the page is
// considered to have expired
//
if (!fRet)
{
GetCurrentGmtTime(lpftExpiryTime);
AddLongLongToFT(lpftExpiryTime, (-1)*ONE_HOUR_DELTA); // subtract 1 hour
fRet = TRUE;
}
}
}
// We found or calculated a valid expiry time, let us check it against the
// server date if possible
FILETIME ft;
length = sizeof(buf) - 1;
index = 0;
if (QueryResponseHeader(HTTP_QUERY_DATE, buf, &length, 0, &index) == ERROR_SUCCESS
&& FParseHttpDate(&ft, buf))
{
// we found a valid Data: header
// if the expires: date is less than or equal to the Date: header
// then we put an expired timestamp on this item.
// Otherwise we let it be the same as was returned by the server.
// This may cause problems due to mismatched clocks between
// the client and the server, but this is the best that can be done.
// Calulating an expires offset from server date causes pages
// coming from proxy cache to expire later, because proxies
// do not change the date: field even if the reponse has been
// sitting the proxy cache for days.
// This behaviour is as-per the HTTP spec.
if (FT2LL(*lpftExpiryTime) <= FT2LL(ft))
{
GetCurrentGmtTime(lpftExpiryTime);
AddLongLongToFT(lpftExpiryTime, (-1)*ONE_HOUR_DELTA); // subtract 1 hour
}
}
if (lpfHasExpiry)
{
*lpfHasExpiry = fRet;
}
if (!fRet)
{
lpftExpiryTime->dwLowDateTime = 0;
lpftExpiryTime->dwHighDateTime = 0;
}
fRet = FALSE;
length = sizeof(buf) - 1;
index = 0;
if (QueryResponseHeader(HTTP_QUERY_LAST_MODIFIED, buf, &length, 0, &index) == ERROR_SUCCESS)
{
DEBUG_PRINT(CACHE,
INFO,
("Last Modified date is: %q\n",
buf
));
fRet = FParseHttpDate(lpftLastModTime, buf);
if (!fRet)
{
DEBUG_PRINT(CACHE,
ERROR,
("FParseHttpDate() returns FALSE\n"
));
}
}
if (lpfHasLastModTime)
{
*lpfHasLastModTime = fRet;
}
if (!fRet)
{
lpftLastModTime->dwLowDateTime = 0;
lpftLastModTime->dwHighDateTime = 0;
}
_ResponseHeaders.UnlockHeaders();
PERF_LOG(PE_TRACE, 0x9002);
DEBUG_LEAVE(0);
}
//
// private functions
//
PRIVATE
VOID
FilterHeaders(
IN LPSTR lpszHeaderInfo,
OUT LPDWORD lpdwHeaderLen
)
/*++
Routine Description:
description-of-function.
Arguments:
lpszHeaderInfo -
lpdwHeaderLen -
Return Value:
None.
--*/
{
PERF_LOG(PE_TRACE, 0x3001);
DWORD i, len, lenT, reduced = 0, dwHeaderTableCount;
LPSTR lpT, lpMark, lpNext, *lprgszHeaderExcludeTable;
//
// skip over the status line
// NB this assumes that the raw buffer is nullterminated
//
if (lpvrgszHeaderExclusionTable) {
lprgszHeaderExcludeTable = lpvrgszHeaderExclusionTable;
dwHeaderTableCount = vdwHeaderExclusionTableCount;
} else {
lprgszHeaderExcludeTable = rgszExcludeHeaders;
dwHeaderTableCount = sizeof(rgszExcludeHeaders) / sizeof(LPSTR);
}
lpT = strchr(lpszHeaderInfo, '\r');
if (!lpT) {
PERF_LOG(PE_TRACE, 0x3002);
return;
}
INET_ASSERT(*(lpT + 1) == '\n');
lpT += 2;
do {
//
// find the header portion
//
lpMark = strchr(lpT, ':');
if (!lpMark) {
break;
}
//
// get the end of the header line
//
lpNext = strchr(lpMark, '\r');
if (!lpNext)
{
INET_ASSERT(FALSE);
// A properly formed header _should_ terminate with \r\n, but sometimes
// that just doesn't happen
lpNext = lpMark;
while (*lpNext)
{
lpNext++;
}
}
else
{
INET_ASSERT(*(lpNext + 1) == '\n');
lpNext += 2;
}
len = (DWORD) PtrDifference(lpMark, lpT) + 1;
lenT = *lpdwHeaderLen; // doing all this to see it properly in debugger
BOOL bFound = FALSE;
for (i = 0; i < dwHeaderTableCount; ++i) {
if (!strnicmp(lpT, lprgszHeaderExcludeTable[i], len)) {
bFound = TRUE;
break;
}
}
if (bFound) {
//
// nuke this header
//
len = lenT - (DWORD)PtrDifference(lpNext, lpszHeaderInfo) + 1; // for NULL character
//
// ACHTUNG memove because of overlapped copies
//
memmove(lpT, lpNext, len);
//
// keep count of how much we reduced the header by
//
reduced += (DWORD) PtrDifference(lpNext, lpT);
//
// lpT is already properly positioned because of the move
//
} else {
lpT = lpNext;
}
} while (TRUE);
*lpdwHeaderLen -= reduced;
PERF_LOG(PE_TRACE, 0x3003);
}
PRIVATE
BOOL
FExcludedMimeType(
IN LPSTR lpszMimeType,
IN DWORD dwMimeTypeSize
)
/*++
Routine Description:
description-of-function.
Arguments:
lpszMimeType -
Return Value:
BOOL
--*/
{
PERF_LOG(PE_TRACE, 0x501);
DWORD i;
LPCSTR * lprgszMimeExcludeTable = rgszExcludedMimeTypes;
DWORD dwMimeExcludeCount = (sizeof(rgszExcludedMimeTypes)/sizeof(LPSTR));
const DWORD *lprgdwMimeExcludeTableOfSizes = rgdwExcludedMimeTypeSizes;
if (lpvrgszMimeExclusionTable) {
lprgszMimeExcludeTable = (LPCSTR *)lpvrgszMimeExclusionTable;
dwMimeExcludeCount = vdwMimeExclusionTableCount;
lprgdwMimeExcludeTableOfSizes = lpvrgdwMimeExclusionTableOfSizes;
}
for (i = 0; i < dwMimeExcludeCount; ++i) {
if ((dwMimeTypeSize == lprgdwMimeExcludeTableOfSizes[i]) &&
!strnicmp(lpszMimeType,
lprgszMimeExcludeTable[i],
lprgdwMimeExcludeTableOfSizes[i])) {
PERF_LOG(PE_TRACE, 0x502);
return TRUE;
}
}
PERF_LOG(PE_TRACE, 0x503);
return FALSE;
}