/* * I F . C P P * * If-xxx header processing and ETags for DAV resources * * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved */ #include "_davprs.h" //$ REVIEW: This file was once the same as \exdav\davif.cpp. //$ REVIEW: These two files should really be merged. They share a lot of //$ REVIEW: common functionality, but they have been evolving separately. //$ REVIEW: We need to be very careful because different bug fixes have //$ REVIEW: been going into each of them. // ETag formation ------------------------------------------------------------ // /* * FETagFromFiletime() * * Purpose: * * Derives an ETag for a given resource or a given last modification * time. * * Parameters: * * pft [in] last modification time * pwszETag [out] ETag buffer * pecb [in] ecb so that we can access the metabase * * Returns: * * TRUE if ETag was created. */ BOOL FETagFromFiletime (FILETIME * pft, LPWSTR pwszEtag, const IEcb * pecb) { Assert (pwszEtag); Assert (pecb); swprintf (pwszEtag, L"\"%x%x%x%x%x%x%x%x:%x\"", (DWORD)(((PUCHAR)pft)[0]), (DWORD)(((PUCHAR)pft)[1]), (DWORD)(((PUCHAR)pft)[2]), (DWORD)(((PUCHAR)pft)[3]), (DWORD)(((PUCHAR)pft)[4]), (DWORD)(((PUCHAR)pft)[5]), (DWORD)(((PUCHAR)pft)[6]), (DWORD)(((PUCHAR)pft)[7]), DwMDChangeNumber(pecb)); return TRUE; } // If-xxx header processing -------------------------------------------------- // SCODE ScCheckEtagAgainstHeader (LPCWSTR pwszEtag, LPCWSTR pwszHeader) { LPCWSTR pwsz; Assert (pwszHeader); // Get the ETag we are going to compare against, and then // look at what was passed it. It should either be an ETAG // or an '*'. We fail if the value does not exist or the // ETag does not match. // HDRITER_W hdri(pwszHeader); for (pwsz = hdri.PszNext(); pwsz; pwsz = hdri.PszNext()) { // Since we do not do week ETAG checking, if the // ETAG starts with "W/" skip those bits // if (L'W' == *pwsz) { Assert (L'/' == pwsz[1]); pwsz += 2; } // If we see stars, then we match // if (L'*' == *pwsz) return S_OK; else { // For DAVFS, we don't do weak matching today // if (pwszEtag && !wcscmp (pwsz, pwszEtag)) return S_OK; } } return E_DAV_IF_HEADER_FAILURE; } SCODE ScCheckFileTimeAgainstHeader (FILETIME * pft, LPCWSTR pwszHeader) { FILETIME ftHeader; FILETIME ftTmp; SYSTEMTIME st; Assert (pft); Assert (pwszHeader); // The header passed in here should be an HTTP-date // of the format "ddd, dd, mmm yyyy HH:mm:ss GMT". // We can spit this into a SYSTEMTIME and then compare // it against the filetime for the resource. // DebugTrace ("DAV: evaluating If-Unmodified-Since header\n"); memset (&st, 0, sizeof(SYSTEMTIME)); if (SUCCEEDED (HrHTTPDateToFileTime(pwszHeader, &ftHeader))) { FILETIME ftCur; // The filetime retrieved from FGetLastModTime is acurate // down to 100-nanosecond increments. The converted date // is acurate down to seconds. Adjust for that. // FileTimeToSystemTime (pft, &st); st.wMilliseconds = 0; SystemTimeToFileTime (&st, &ftTmp); // Get current time // GetSystemTimeAsFileTime(&ftCur); // Compare the two filetimes // Note that we also need to make sure the Modified-Since time is // less than our current time // if ((CompareFileTime (&ftHeader, &ftTmp) >= 0) && (CompareFileTime (&ftHeader, &ftCur) < 0)) return S_OK; return E_DAV_IF_HEADER_FAILURE; } return S_FALSE; } SCODE ScCheckIfHeaders(IMethUtil * pmu, FILETIME * pft, BOOL fGetMethod) { Assert(pmu); WCHAR pwszEtag[CCH_ETAG]; SideAssert(FETagFromFiletime (pft, pwszEtag, pmu->GetEcb())); return ScCheckIfHeadersFromEtag (pmu, pft, fGetMethod, pwszEtag); } SCODE ScCheckIfHeadersFromEtag (IMethUtil * pmu, FILETIME* pft, BOOL fGetMethod, LPCWSTR pwszEtag) { SCODE sc = S_OK; LPCWSTR pwsz; Assert (pmu); Assert (pft); Assert (pwszEtag); // There're several bugs related with DAV not matching IIS behavior // on these If-xxx header processing. // So we now just copy their logic over // Check the 'if-match' header // if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Match, FALSE)) != NULL) { DebugTrace ("DAV: evaluating 'if-match' header\n"); sc = ScCheckEtagAgainstHeader (pwszEtag, pwsz); if (FAILED (sc)) goto ret; } // Now see if we have an If-None-Match, and if so handle that. // BOOL fIsNoneMatchPassed = TRUE; BOOL fSkipIfModifiedSince = FALSE; if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_None_Match, FALSE)) != NULL) { DebugTrace ("DAV: evaluating 'if-none-match' header\n"); if (!FAILED (ScCheckEtagAgainstHeader (pwszEtag, pwsz))) { // Etag match, so nonmatch test is NOT passed // fIsNoneMatchPassed = FALSE; } else { fSkipIfModifiedSince = TRUE; } } // The "if-modified-since" really only applies to GET-type // requests // if (!fSkipIfModifiedSince && fGetMethod) { if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Modified_Since, FALSE)) != NULL) { DebugTrace ("DAV: evaluating 'if-none-match' header\n"); if (S_OK == ScCheckFileTimeAgainstHeader (pft, pwsz)) { sc = fGetMethod ? E_DAV_ENTITY_NOT_MODIFIED : E_DAV_IF_HEADER_FAILURE; goto ret; } fIsNoneMatchPassed = TRUE; } } if (!fIsNoneMatchPassed) { sc = fGetMethod ? E_DAV_ENTITY_NOT_MODIFIED : E_DAV_IF_HEADER_FAILURE; goto ret; } // Made it through that, handle If-Unmodified-Since if we have that. // if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Unmodified_Since, FALSE)) != NULL) { DebugTrace ("DAV: evaluating 'if-unmodified-since' header\n"); sc = ScCheckFileTimeAgainstHeader (pft, pwsz); if (FAILED (sc)) goto ret; } ret: if (sc == E_DAV_ENTITY_NOT_MODIFIED) { // Let me quote from the HTTP/1.1 draft... // // "The response MUST include the following header fields: // // ... // // . ETag and/or Content-Location, if the header would have been sent in // a 200 response to the same request // // ..." // // So what that means, is that we really just want to do is suppress the // body of the response, set a 304 error code and do everything else as // normal. All of which is done by setting a hsc of 304. // DebugTrace ("Dav: suppressing body for 304 response\n"); pmu->SetResponseCode (HSC_NOT_MODIFIED, NULL, 0); sc = S_OK; } return sc; } SCODE ScCheckIfRangeHeader (IMethUtil * pmu, FILETIME * pft) { Assert(pmu); WCHAR pwszEtag[CCH_ETAG]; SideAssert(FETagFromFiletime (pft, pwszEtag, pmu->GetEcb())); return ScCheckIfRangeHeaderFromEtag (pmu, pft, pwszEtag); } SCODE ScCheckIfRangeHeaderFromEtag (IMethUtil * pmu, FILETIME * pft, LPCWSTR pwszEtag) { SCODE sc = S_OK; LPCWSTR pwsz; Assert (pmu); Assert (pft); Assert (pwszEtag); // Check "If-Range". Do not apply URL conversion rules to this header // if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Range, FALSE)) != NULL) { DebugTrace ("DAV: evaluating 'if-range' header\n"); // The format of this header is either an ETAG or a // date. Process accordingly... // if ((L'"' == *pwsz) || (L'"' == *(pwsz + 2))) { if (L'W' == *pwsz) { Assert (L'/' == *(pwsz + 1)); pwsz += 2; } sc = ScCheckEtagAgainstHeader (pwszEtag, pwsz); if (FAILED (sc)) goto ret; } else { sc = ScCheckFileTimeAgainstHeader (pft, pwsz); goto ret; } } ret: return sc; }