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.

325 lines
7.7 KiB

  1. /*
  2. * I F . C P P
  3. *
  4. * If-xxx header processing and ETags for DAV resources
  5. *
  6. * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
  7. */
  8. #include "_davprs.h"
  9. //$ REVIEW: This file was once the same as \exdav\davif.cpp.
  10. //$ REVIEW: These two files should really be merged. They share a lot of
  11. //$ REVIEW: common functionality, but they have been evolving separately.
  12. //$ REVIEW: We need to be very careful because different bug fixes have
  13. //$ REVIEW: been going into each of them.
  14. // ETag formation ------------------------------------------------------------
  15. //
  16. /*
  17. * FETagFromFiletime()
  18. *
  19. * Purpose:
  20. *
  21. * Derives an ETag for a given resource or a given last modification
  22. * time.
  23. *
  24. * Parameters:
  25. *
  26. * pft [in] last modification time
  27. * pwszETag [out] ETag buffer
  28. * pecb [in] ecb so that we can access the metabase
  29. *
  30. * Returns:
  31. *
  32. * TRUE if ETag was created.
  33. */
  34. BOOL
  35. FETagFromFiletime (FILETIME * pft, LPWSTR pwszEtag, const IEcb * pecb)
  36. {
  37. Assert (pwszEtag);
  38. Assert (pecb);
  39. swprintf (pwszEtag,
  40. L"\"%x%x%x%x%x%x%x%x:%x\"",
  41. (DWORD)(((PUCHAR)pft)[0]),
  42. (DWORD)(((PUCHAR)pft)[1]),
  43. (DWORD)(((PUCHAR)pft)[2]),
  44. (DWORD)(((PUCHAR)pft)[3]),
  45. (DWORD)(((PUCHAR)pft)[4]),
  46. (DWORD)(((PUCHAR)pft)[5]),
  47. (DWORD)(((PUCHAR)pft)[6]),
  48. (DWORD)(((PUCHAR)pft)[7]),
  49. DwMDChangeNumber(pecb));
  50. return TRUE;
  51. }
  52. // If-xxx header processing --------------------------------------------------
  53. //
  54. SCODE
  55. ScCheckEtagAgainstHeader (LPCWSTR pwszEtag, LPCWSTR pwszHeader)
  56. {
  57. LPCWSTR pwsz;
  58. Assert (pwszHeader);
  59. // Get the ETag we are going to compare against, and then
  60. // look at what was passed it. It should either be an ETAG
  61. // or an '*'. We fail if the value does not exist or the
  62. // ETag does not match.
  63. //
  64. HDRITER_W hdri(pwszHeader);
  65. for (pwsz = hdri.PszNext(); pwsz; pwsz = hdri.PszNext())
  66. {
  67. // Since we do not do week ETAG checking, if the
  68. // ETAG starts with "W/" skip those bits
  69. //
  70. if (L'W' == *pwsz)
  71. {
  72. Assert (L'/' == pwsz[1]);
  73. pwsz += 2;
  74. }
  75. // If we see stars, then we match
  76. //
  77. if (L'*' == *pwsz)
  78. return S_OK;
  79. else
  80. {
  81. // For DAVFS, we don't do weak matching today
  82. //
  83. if (pwszEtag && !wcscmp (pwsz, pwszEtag))
  84. return S_OK;
  85. }
  86. }
  87. return E_DAV_IF_HEADER_FAILURE;
  88. }
  89. SCODE
  90. ScCheckFileTimeAgainstHeader (FILETIME * pft, LPCWSTR pwszHeader)
  91. {
  92. FILETIME ftHeader;
  93. FILETIME ftTmp;
  94. SYSTEMTIME st;
  95. Assert (pft);
  96. Assert (pwszHeader);
  97. // The header passed in here should be an HTTP-date
  98. // of the format "ddd, dd, mmm yyyy HH:mm:ss GMT".
  99. // We can spit this into a SYSTEMTIME and then compare
  100. // it against the filetime for the resource.
  101. //
  102. DebugTrace ("DAV: evaluating If-Unmodified-Since header\n");
  103. memset (&st, 0, sizeof(SYSTEMTIME));
  104. if (SUCCEEDED (HrHTTPDateToFileTime(pwszHeader, &ftHeader)))
  105. {
  106. FILETIME ftCur;
  107. // The filetime retrieved from FGetLastModTime is acurate
  108. // down to 100-nanosecond increments. The converted date
  109. // is acurate down to seconds. Adjust for that.
  110. //
  111. FileTimeToSystemTime (pft, &st);
  112. st.wMilliseconds = 0;
  113. SystemTimeToFileTime (&st, &ftTmp);
  114. // Get current time
  115. //
  116. GetSystemTimeAsFileTime(&ftCur);
  117. // Compare the two filetimes
  118. // Note that we also need to make sure the Modified-Since time is
  119. // less than our current time
  120. //
  121. if ((CompareFileTime (&ftHeader, &ftTmp) >= 0) &&
  122. (CompareFileTime (&ftHeader, &ftCur) < 0))
  123. return S_OK;
  124. return E_DAV_IF_HEADER_FAILURE;
  125. }
  126. return S_FALSE;
  127. }
  128. SCODE
  129. ScCheckIfHeaders(IMethUtil * pmu,
  130. FILETIME * pft,
  131. BOOL fGetMethod)
  132. {
  133. Assert(pmu);
  134. WCHAR pwszEtag[CCH_ETAG];
  135. SideAssert(FETagFromFiletime (pft, pwszEtag, pmu->GetEcb()));
  136. return ScCheckIfHeadersFromEtag (pmu,
  137. pft,
  138. fGetMethod,
  139. pwszEtag);
  140. }
  141. SCODE
  142. ScCheckIfHeadersFromEtag (IMethUtil * pmu,
  143. FILETIME* pft,
  144. BOOL fGetMethod,
  145. LPCWSTR pwszEtag)
  146. {
  147. SCODE sc = S_OK;
  148. LPCWSTR pwsz;
  149. Assert (pmu);
  150. Assert (pft);
  151. Assert (pwszEtag);
  152. // There're several bugs related with DAV not matching IIS behavior
  153. // on these If-xxx header processing.
  154. // So we now just copy their logic over
  155. // Check the 'if-match' header
  156. //
  157. if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Match, FALSE)) != NULL)
  158. {
  159. DebugTrace ("DAV: evaluating 'if-match' header\n");
  160. sc = ScCheckEtagAgainstHeader (pwszEtag, pwsz);
  161. if (FAILED (sc))
  162. goto ret;
  163. }
  164. // Now see if we have an If-None-Match, and if so handle that.
  165. //
  166. BOOL fIsNoneMatchPassed = TRUE;
  167. BOOL fSkipIfModifiedSince = FALSE;
  168. if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_None_Match, FALSE)) != NULL)
  169. {
  170. DebugTrace ("DAV: evaluating 'if-none-match' header\n");
  171. if (!FAILED (ScCheckEtagAgainstHeader (pwszEtag, pwsz)))
  172. {
  173. // Etag match, so nonmatch test is NOT passed
  174. //
  175. fIsNoneMatchPassed = FALSE;
  176. }
  177. else
  178. {
  179. fSkipIfModifiedSince = TRUE;
  180. }
  181. }
  182. // The "if-modified-since" really only applies to GET-type
  183. // requests
  184. //
  185. if (!fSkipIfModifiedSince && fGetMethod)
  186. {
  187. if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Modified_Since, FALSE)) != NULL)
  188. {
  189. DebugTrace ("DAV: evaluating 'if-none-match' header\n");
  190. if (S_OK == ScCheckFileTimeAgainstHeader (pft, pwsz))
  191. {
  192. sc = fGetMethod
  193. ? E_DAV_ENTITY_NOT_MODIFIED
  194. : E_DAV_IF_HEADER_FAILURE;
  195. goto ret;
  196. }
  197. fIsNoneMatchPassed = TRUE;
  198. }
  199. }
  200. if (!fIsNoneMatchPassed)
  201. {
  202. sc = fGetMethod
  203. ? E_DAV_ENTITY_NOT_MODIFIED
  204. : E_DAV_IF_HEADER_FAILURE;
  205. goto ret;
  206. }
  207. // Made it through that, handle If-Unmodified-Since if we have that.
  208. //
  209. if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Unmodified_Since, FALSE)) != NULL)
  210. {
  211. DebugTrace ("DAV: evaluating 'if-unmodified-since' header\n");
  212. sc = ScCheckFileTimeAgainstHeader (pft, pwsz);
  213. if (FAILED (sc))
  214. goto ret;
  215. }
  216. ret:
  217. if (sc == E_DAV_ENTITY_NOT_MODIFIED)
  218. {
  219. // Let me quote from the HTTP/1.1 draft...
  220. //
  221. // "The response MUST include the following header fields:
  222. //
  223. // ...
  224. //
  225. // . ETag and/or Content-Location, if the header would have been sent in
  226. // a 200 response to the same request
  227. //
  228. // ..."
  229. //
  230. // So what that means, is that we really just want to do is suppress the
  231. // body of the response, set a 304 error code and do everything else as
  232. // normal. All of which is done by setting a hsc of 304.
  233. //
  234. DebugTrace ("Dav: suppressing body for 304 response\n");
  235. pmu->SetResponseCode (HSC_NOT_MODIFIED, NULL, 0);
  236. sc = S_OK;
  237. }
  238. return sc;
  239. }
  240. SCODE
  241. ScCheckIfRangeHeader (IMethUtil * pmu, FILETIME * pft)
  242. {
  243. Assert(pmu);
  244. WCHAR pwszEtag[CCH_ETAG];
  245. SideAssert(FETagFromFiletime (pft, pwszEtag, pmu->GetEcb()));
  246. return ScCheckIfRangeHeaderFromEtag (pmu, pft, pwszEtag);
  247. }
  248. SCODE
  249. ScCheckIfRangeHeaderFromEtag (IMethUtil * pmu, FILETIME * pft, LPCWSTR pwszEtag)
  250. {
  251. SCODE sc = S_OK;
  252. LPCWSTR pwsz;
  253. Assert (pmu);
  254. Assert (pft);
  255. Assert (pwszEtag);
  256. // Check "If-Range". Do not apply URL conversion rules to this header
  257. //
  258. if ((pwsz = pmu->LpwszGetRequestHeader (gc_szIf_Range, FALSE)) != NULL)
  259. {
  260. DebugTrace ("DAV: evaluating 'if-range' header\n");
  261. // The format of this header is either an ETAG or a
  262. // date. Process accordingly...
  263. //
  264. if ((L'"' == *pwsz) || (L'"' == *(pwsz + 2)))
  265. {
  266. if (L'W' == *pwsz)
  267. {
  268. Assert (L'/' == *(pwsz + 1));
  269. pwsz += 2;
  270. }
  271. sc = ScCheckEtagAgainstHeader (pwszEtag, pwsz);
  272. if (FAILED (sc))
  273. goto ret;
  274. }
  275. else
  276. {
  277. sc = ScCheckFileTimeAgainstHeader (pft, pwsz);
  278. goto ret;
  279. }
  280. }
  281. ret:
  282. return sc;
  283. }