/*++ Copyright (c) 2000 Microsoft Corporation Module Name : cachevalidation.cxx Abstract: Handle Cache Validation (If-* headers) Author: Anil Ruia (AnilR) 3-Apr-2000 Environment: Win32 - User Mode Project: UlW3.dll --*/ #include "precomp.hxx" #include "staticfile.hxx" dllexp BOOL FindInETagList(LPCSTR pLocalETag, LPCSTR pETagList, BOOL fWeakCompare) /*++ Routine Description: Search input list of ETags for one that matches our local ETag. Arguments: pLocalETag - The local ETag we're using. pETagList - The ETag list we've received from the client. bWeakCompare - Whether using Weak Comparison is ok Returns: TRUE if we found a matching ETag, FALSE otherwise. --*/ { UINT QuoteCount; LPCSTR pFileETag; BOOL Matched; // We'll loop through the ETag string, looking for ETag to // compare, as long as we have an ETag to look at. do { while (SAFEIsSpace(*pETagList)) { pETagList++; } if (!*pETagList) { // Ran out of ETag. return FALSE; } // If this ETag is *, it's a match. if (*pETagList == '*') { return TRUE; } // See if this ETag is weak. if (pETagList[0] == 'W' && pETagList[1] == '/') { // This is a weak validator. If we're not doing the weak // comparison, fail. if (!fWeakCompare) { return FALSE; } // Skip over the 'W/', and any intervening whitespace. pETagList += 2; while (SAFEIsSpace(*pETagList)) { pETagList++; } if (!*pETagList) { // Ran out of ETag. return FALSE; } } if (*pETagList != '"') { // This isn't a quoted string, so fail. return FALSE; } // OK, right now we should be at the start of a quoted string that // we can compare against our current ETag. QuoteCount = 0; Matched = TRUE; pFileETag = pLocalETag; // Do the actual compare. We do this by scanning the current ETag, // which is a quoted string. We look for two quotation marks, the // the delimiters if the quoted string. If after we find two quotes // in the ETag everything has matched, then we've matched this ETag. // Otherwise we'll try the next one. do { CHAR Temp; Temp = *pETagList; if (Temp == '"') { QuoteCount++; } if (*pFileETag != Temp) { Matched = FALSE; } if (!Temp) { return FALSE; } pETagList++; if (*pFileETag == '\0') { break; } pFileETag++; } while (QuoteCount != 2); if (Matched) { return TRUE; } // Otherwise, at this point we need to look at the next ETag. while (QuoteCount != 2) { if (*pETagList == '"') { QuoteCount++; } else { if (*pETagList == '\0') { return FALSE; } } pETagList++; } while (SAFEIsSpace(*pETagList)) { pETagList++; } if (*pETagList == ',') { pETagList++; } else { return FALSE; } } while ( *pETagList ); return FALSE; } HRESULT W3_STATIC_FILE_HANDLER::CacheValidationDoWork( W3_CONTEXT *pW3Context, W3_FILE_INFO *pOpenFile, BOOL *pfHandled) /*++ Synopsis Handle the Cache Related If-* headers Input pW3Context : W3_CONTEXT for the request pOpenFile : The file's cache entry pfHandled : On return indicates whether, we have handled the request or further processing needs to be done Returns HRESULT --*/ { W3_RESPONSE *pResponse = pW3Context->QueryResponse(); W3_REQUEST *pRequest = pW3Context->QueryRequest(); // // There are currently 4 possible Cache Related If-* modifiers: // If-Match, If-Unmodified-Since, If-Non-Match, If-Modified-Since. // We handle them in that order if all are present, and as soon as // one condition fails we stop processing // // // Now handle the If-Match header, if we have one. // LPCSTR pszIfMatch = pRequest->GetHeader(HttpHeaderIfMatch); if (pszIfMatch != NULL) { if (pOpenFile->QueryIsWeakETag() || !FindInETagList(pOpenFile->QueryETag(), pszIfMatch, FALSE)) { pResponse->ClearHeaders(); pResponse->SetStatus(HttpStatusPreconditionFailed); *pfHandled = TRUE; return S_OK; } } // // Now see if we have an If-None-Match, and if so handle that. // LPCSTR pszIfNoneMatch = pRequest->GetHeader(HttpHeaderIfNoneMatch); BOOL fIsNoneMatchPassed = TRUE; BOOL fSkipIfModifiedSince = FALSE; if (pszIfNoneMatch != NULL) { if (FindInETagList(pOpenFile->QueryETag(), pszIfNoneMatch, TRUE)) { fIsNoneMatchPassed = FALSE; } else { // If none of the tags match, we should skip If-Modified-Since fSkipIfModifiedSince = TRUE; } } // // Made it through that, handle If-Modified-Since if we have that. // LPCSTR pszIfModifiedSince = pRequest->GetHeader(HttpHeaderIfModifiedSince); if (!fSkipIfModifiedSince && pszIfModifiedSince != NULL) { LARGE_INTEGER liModifiedSince; if (StringTimeToFileTime(pszIfModifiedSince, &liModifiedSince)) { FILETIME tm; pOpenFile->QueryLastWriteTime(&tm); // Check if our last write time is greater than their // ModifiedSince time if (*(LONGLONG*)&tm <= liModifiedSince.QuadPart) { // Need to check and see if the Modified-Since time is greater // than our current time. If it is, we ignore it. GetSystemTimeAsFileTime(&tm); if (*(LONGLONG *)&tm >= liModifiedSince.QuadPart) { // // Clear out any content-encoding set since we are not // sending the actual body // pResponse->SetHeaderByReference(HttpHeaderContentEncoding, NULL, 0); pResponse->SetStatus(HttpStatusNotModified); *pfHandled = TRUE; return S_OK; } } } fIsNoneMatchPassed = TRUE; } if (!fIsNoneMatchPassed) { // // Clear out any content-encoding set since we are not // sending the actual body // pResponse->SetHeaderByReference(HttpHeaderContentEncoding, NULL, 0); pResponse->SetStatus(HttpStatusNotModified); *pfHandled = TRUE; return S_OK; } // // Made it through that, handle If-Unmodified-Since if we have that. // LPCSTR pszIfUnmodifiedSince = pRequest->GetHeader( HttpHeaderIfUnmodifiedSince); if (pszIfUnmodifiedSince != NULL) { LARGE_INTEGER liUnmodifiedSince; if (StringTimeToFileTime(pszIfUnmodifiedSince, &liUnmodifiedSince)) { FILETIME tm; pOpenFile->QueryLastWriteTime(&tm); // If our last write time is greater than their UnmodifiedSince // time, the precondition fails. if (*(LONGLONG*)&tm > liUnmodifiedSince.QuadPart) { pResponse->ClearHeaders(); pResponse->SetStatus(HttpStatusPreconditionFailed); *pfHandled = TRUE; return S_OK; } } } *pfHandled = FALSE; return S_OK; }