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.

676 lines
19 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name :
  4. range.cxx
  5. Abstract:
  6. Handle Range Requests
  7. Author:
  8. Anil Ruia (AnilR) 29-Mar-2000
  9. Environment:
  10. Win32 - User Mode
  11. Project:
  12. UlW3.dll
  13. --*/
  14. #include "precomp.hxx"
  15. #include "staticfile.hxx"
  16. #define RANGE_BOUNDARY "<q1w2e3r4t5y6u7i8o9p0zaxscdvfbgnhmjklkl>"
  17. #define MAX_RANGE_ALLOWED 5
  18. extern BOOL FindInETagList(LPCSTR pLocalETag,
  19. LPCSTR pETagList,
  20. BOOL fWeakCompare);
  21. STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeContentType;
  22. STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeMidDelimiter;
  23. STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeEndDelimiter;
  24. DWORD W3_STATIC_FILE_HANDLER::sm_dwMaxRangeAllowed = MAX_RANGE_ALLOWED;
  25. // static
  26. HRESULT W3_STATIC_FILE_HANDLER::Initialize()
  27. {
  28. ALLOC_CACHE_CONFIGURATION acConfig;
  29. HRESULT hr = NO_ERROR;
  30. //
  31. // Setup allocation lookaside
  32. //
  33. acConfig.nConcurrency = 1;
  34. acConfig.nThreshold = 100;
  35. acConfig.cbSize = sizeof( W3_STATIC_FILE_HANDLER );
  36. DBG_ASSERT( sm_pachStaticFileHandlers == NULL );
  37. sm_pachStaticFileHandlers = new ALLOC_CACHE_HANDLER( "W3_STATIC_FILE_HANDLER",
  38. &acConfig );
  39. if ( sm_pachStaticFileHandlers == NULL )
  40. {
  41. hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
  42. goto Failed;
  43. }
  44. //
  45. // Initialize the various Range Strings
  46. //
  47. sm_pstrRangeContentType = new STRA;
  48. sm_pstrRangeMidDelimiter = new STRA;
  49. sm_pstrRangeEndDelimiter = new STRA;
  50. if (sm_pstrRangeContentType == NULL ||
  51. sm_pstrRangeMidDelimiter == NULL ||
  52. sm_pstrRangeEndDelimiter == NULL)
  53. {
  54. hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  55. goto Failed;
  56. }
  57. if (FAILED(hr = sm_pstrRangeContentType->Copy(
  58. "multipart/byteranges; boundary=")) ||
  59. FAILED(hr = sm_pstrRangeContentType->Append(RANGE_BOUNDARY)))
  60. {
  61. goto Failed;
  62. }
  63. if (FAILED(hr = sm_pstrRangeMidDelimiter->Copy("--")) ||
  64. FAILED(hr = sm_pstrRangeMidDelimiter->Append(RANGE_BOUNDARY)) ||
  65. FAILED(hr = sm_pstrRangeMidDelimiter->Append("\r\n")))
  66. {
  67. goto Failed;
  68. }
  69. if (FAILED(hr = sm_pstrRangeEndDelimiter->Copy("\r\n--")) ||
  70. FAILED(hr = sm_pstrRangeEndDelimiter->Append(RANGE_BOUNDARY)) ||
  71. FAILED(hr = sm_pstrRangeEndDelimiter->Append("--\r\n\r\n")))
  72. {
  73. goto Failed;
  74. }
  75. HKEY w3Params;
  76. if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  77. W3_PARAMETERS_KEY,
  78. 0,
  79. KEY_READ,
  80. &w3Params) == NO_ERROR)
  81. {
  82. DWORD dwType;
  83. DWORD cbData = sizeof DWORD;
  84. if ((RegQueryValueEx(w3Params,
  85. L"MaximumRangeAllowed",
  86. NULL,
  87. &dwType,
  88. (LPBYTE)&sm_dwMaxRangeAllowed,
  89. &cbData) != NO_ERROR) ||
  90. (dwType != REG_DWORD))
  91. {
  92. sm_dwMaxRangeAllowed = MAX_RANGE_ALLOWED;
  93. }
  94. RegCloseKey(w3Params);
  95. }
  96. return S_OK;
  97. Failed:
  98. Terminate();
  99. return hr;
  100. }
  101. BOOL ScanRange(LPCSTR *ppszRange,
  102. ULARGE_INTEGER liFileSize,
  103. ULARGE_INTEGER *pliOffset,
  104. ULARGE_INTEGER *pliSize,
  105. BOOL *pfInvalidRange)
  106. /*++
  107. Routine Description:
  108. Scan the next range in strRange
  109. Returns:
  110. TRUE if a range was found, else FALSE
  111. Arguments:
  112. ppszRange String pointing to the current range.
  113. liFileSize Size of the file being retrieved
  114. pliOffset range offset on return
  115. pliSizeTo range size on return
  116. pfInvalidRange set to TRUE on return if Invalid syntax
  117. --*/
  118. {
  119. LPCSTR pszRange = *ppszRange;
  120. //
  121. // Skip to begining of next range
  122. //
  123. while (*pszRange == ' ')
  124. {
  125. ++pszRange;
  126. }
  127. //
  128. // Test for no range
  129. //
  130. if (*pszRange == '\0')
  131. {
  132. return FALSE;
  133. }
  134. //
  135. // determine Offset & Size to send
  136. //
  137. if (*pszRange == '-')
  138. {
  139. //
  140. // This is in format -nnn which means send bytes filesize-nnn
  141. // to eof
  142. //
  143. ++pszRange;
  144. if (!isdigit(*pszRange))
  145. {
  146. *pfInvalidRange = TRUE;
  147. return TRUE;
  148. }
  149. pliSize->QuadPart = _atoi64(pszRange);
  150. if (pliSize->QuadPart > liFileSize.QuadPart)
  151. {
  152. pliSize->QuadPart = liFileSize.QuadPart;
  153. pliOffset->QuadPart = 0;
  154. }
  155. else
  156. {
  157. pliOffset->QuadPart = liFileSize.QuadPart - pliSize->QuadPart;
  158. }
  159. }
  160. else if (isdigit(*pszRange))
  161. {
  162. //
  163. // This is in format mmm-nnn which menas send bytes mmm to nnn
  164. // or format mmm- which means send bytes mmm to eof
  165. //
  166. pliOffset->QuadPart = _atoi64(pszRange);
  167. //
  168. // Skip over the beginning number - and any intervening spaces
  169. //
  170. while(isdigit(*pszRange))
  171. {
  172. pszRange++;
  173. }
  174. while(*pszRange == ' ')
  175. {
  176. pszRange++;
  177. }
  178. if (*pszRange != '-')
  179. {
  180. *pfInvalidRange = TRUE;
  181. return TRUE;
  182. }
  183. pszRange++;
  184. while(*pszRange == ' ')
  185. {
  186. pszRange++;
  187. }
  188. if (isdigit(*pszRange))
  189. {
  190. //
  191. // We have mmm-nnn
  192. //
  193. ULARGE_INTEGER liEnd;
  194. liEnd.QuadPart = _atoi64(pszRange);
  195. if (liEnd.QuadPart < pliOffset->QuadPart)
  196. {
  197. *pfInvalidRange = TRUE;
  198. return TRUE;
  199. }
  200. if (liEnd.QuadPart >= liFileSize.QuadPart)
  201. {
  202. pliSize->QuadPart = liFileSize.QuadPart - pliOffset->QuadPart;
  203. }
  204. else
  205. {
  206. pliSize->QuadPart = liEnd.QuadPart - pliOffset->QuadPart + 1;
  207. }
  208. }
  209. else
  210. {
  211. //
  212. // We have mmm-
  213. //
  214. pliSize->QuadPart = liFileSize.QuadPart - pliOffset->QuadPart;
  215. }
  216. }
  217. else
  218. {
  219. //
  220. // Invalid Syntax
  221. //
  222. *pfInvalidRange = TRUE;
  223. return TRUE;
  224. }
  225. //
  226. // Skip to the start of the next range
  227. //
  228. while (isdigit(*pszRange))
  229. {
  230. ++pszRange;
  231. }
  232. while (*pszRange == ' ')
  233. {
  234. ++pszRange;
  235. }
  236. if (*pszRange == ',')
  237. {
  238. ++pszRange;
  239. }
  240. else if (*pszRange != '\0')
  241. {
  242. *pfInvalidRange = TRUE;
  243. return TRUE;
  244. }
  245. *ppszRange = pszRange;
  246. return TRUE;
  247. }
  248. HRESULT W3_STATIC_FILE_HANDLER::RangeDoWork(W3_CONTEXT *pW3Context,
  249. W3_FILE_INFO *pOpenFile,
  250. BOOL *pfHandled)
  251. {
  252. W3_REQUEST *pRequest = pW3Context->QueryRequest();
  253. W3_RESPONSE *pResponse = pW3Context->QueryResponse();
  254. // First, handle If-Range: if it exists. If the If-Range matches we
  255. // don't do anything. If the If-Range doesn't match then we force
  256. // retrieval of the whole file.
  257. LPCSTR pszIfRange = pRequest->GetHeader(HttpHeaderIfRange);
  258. if (pszIfRange != NULL && *pszIfRange != L'\0')
  259. {
  260. // Need to determine if what we have is a date or an ETag.
  261. // An ETag may start with a W/ or a quote. A date may start
  262. // with a W but will never have the second character be a /.
  263. if (*pszIfRange == L'"' ||
  264. (*pszIfRange == L'W' && pszIfRange[1] == L'/'))
  265. {
  266. // This is an ETag.
  267. if (pOpenFile->QueryIsWeakETag() ||
  268. !FindInETagList(pOpenFile->QueryETag(),
  269. pszIfRange,
  270. FALSE))
  271. {
  272. // The If-Range failed, so we can't send a range. Force
  273. // sending the whole thing.
  274. *pfHandled = FALSE;
  275. return S_OK;
  276. }
  277. }
  278. else
  279. {
  280. // This is a date
  281. LARGE_INTEGER liRangeTime;
  282. // This must be a date. Convert it to a time, and see if it's
  283. // less than or equal to our last write time. If it is, the
  284. // file's changed, and we can't perform the range.
  285. if (!StringTimeToFileTime(pszIfRange, &liRangeTime))
  286. {
  287. // Couldn't convert it, so don't send the range.
  288. *pfHandled = FALSE;
  289. return S_OK;
  290. }
  291. else
  292. {
  293. FILETIME tm;
  294. pOpenFile->QueryLastWriteTime(&tm);
  295. if (*(LONGLONG*)&tm > liRangeTime.QuadPart )
  296. {
  297. // The If-Range failed, so we can't send a range. Force
  298. // sending the whole thing.
  299. *pfHandled = FALSE;
  300. return S_OK;
  301. }
  302. }
  303. }
  304. }
  305. // If we fell through, then If-Range passed, we are going to try sending
  306. // a range response
  307. LPCSTR pszRange = pRequest->GetHeader(HttpHeaderRange);
  308. //
  309. // We have bytes = <range1>, <range2>, ...
  310. // skip past the '='
  311. //
  312. pszRange = strchr(pszRange, '=');
  313. if (pszRange == NULL)
  314. {
  315. // Invalid syntax, send entire file
  316. *pfHandled = FALSE;
  317. return S_OK;
  318. }
  319. pszRange++;
  320. ULARGE_INTEGER liFileSize;
  321. HRESULT hr = S_OK;
  322. pOpenFile->QuerySize(&liFileSize);
  323. if (liFileSize.QuadPart <= 0)
  324. {
  325. pResponse->ClearHeaders();
  326. pResponse->SetStatus(HttpStatusRangeNotSatisfiable);
  327. CHAR pszContentRange[32];
  328. strcpy(pszContentRange, "bytes */");
  329. _i64toa(liFileSize.QuadPart, pszContentRange + 8, 10);
  330. if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange,
  331. pszContentRange,
  332. (USHORT)strlen(pszContentRange))))
  333. {
  334. return hr;
  335. }
  336. *pfHandled = TRUE;
  337. return S_OK;
  338. }
  339. DWORD cRanges = 0;
  340. STACK_BUFFER( bufByteRange, 256);
  341. HTTP_BYTE_RANGE *pByteRange;
  342. ULARGE_INTEGER liRangeOffset;
  343. ULARGE_INTEGER liRangeSize;
  344. BOOL fInvalidSyntax = FALSE;
  345. BOOL fAtLeastOneRange = FALSE;
  346. ULONGLONG dwTotalRangeBytes = 0;
  347. while (ScanRange(&pszRange,
  348. liFileSize,
  349. &liRangeOffset,
  350. &liRangeSize,
  351. &fInvalidSyntax))
  352. {
  353. fAtLeastOneRange = TRUE;
  354. if (fInvalidSyntax)
  355. {
  356. //
  357. // Invalid syntax in Range header. Ignore Range headers,
  358. // Send complete File.
  359. //
  360. *pfHandled = FALSE;
  361. return S_OK;
  362. }
  363. if (liRangeOffset.QuadPart > liFileSize.QuadPart ||
  364. liRangeSize.QuadPart == 0)
  365. {
  366. //
  367. // The Range is not satisfiable
  368. //
  369. continue;
  370. }
  371. cRanges++;
  372. if (cRanges * sizeof(HTTP_BYTE_RANGE) > bufByteRange.QuerySize())
  373. {
  374. if (!bufByteRange.Resize(cRanges * sizeof(HTTP_BYTE_RANGE), 256))
  375. {
  376. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  377. }
  378. }
  379. pByteRange = (HTTP_BYTE_RANGE *)bufByteRange.QueryPtr();
  380. pByteRange[cRanges - 1].StartingOffset.QuadPart = liRangeOffset.QuadPart;
  381. pByteRange[cRanges - 1].Length.QuadPart = liRangeSize.QuadPart;
  382. //
  383. // Keep track of the total bytes in all the ranges combined
  384. //
  385. dwTotalRangeBytes += liRangeSize.QuadPart;
  386. }
  387. if (dwTotalRangeBytes > (liFileSize.QuadPart * sm_dwMaxRangeAllowed))
  388. {
  389. pResponse->ClearHeaders();
  390. pResponse->SetStatus(HttpStatusBadRequest);
  391. *pfHandled = TRUE;
  392. return S_OK;
  393. }
  394. if (!fAtLeastOneRange)
  395. {
  396. //
  397. // Syntactically invalid, ignore the range header
  398. //
  399. *pfHandled = FALSE;
  400. return S_OK;
  401. }
  402. if (cRanges == 0)
  403. {
  404. //
  405. // No byte ranges are satisfiable
  406. //
  407. pResponse->ClearHeaders();
  408. pResponse->SetStatus(HttpStatusRangeNotSatisfiable);
  409. CHAR pszContentRange[32];
  410. strcpy(pszContentRange, "bytes */");
  411. _i64toa(liFileSize.QuadPart, pszContentRange + 8, 10);
  412. if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange,
  413. pszContentRange,
  414. (USHORT)strlen(pszContentRange))))
  415. {
  416. return hr;
  417. }
  418. *pfHandled = TRUE;
  419. return S_OK;
  420. }
  421. *pfHandled = TRUE;
  422. pResponse->SetStatus(HttpStatusPartialContent);
  423. STRA *pstrContentType = pW3Context->QueryUrlContext()->QueryUrlInfo()->
  424. QueryContentType();
  425. STACK_STRA ( strPartContentType, 128);
  426. if (cRanges == 1)
  427. {
  428. //
  429. // Only one chunk, Content-Type is same as that of complete entity
  430. //
  431. if (FAILED(hr = pResponse->SetHeaderByReference(
  432. HttpHeaderContentType,
  433. pstrContentType->QueryStr(),
  434. (USHORT)pstrContentType->QueryCCH())))
  435. {
  436. return hr;
  437. }
  438. }
  439. else
  440. {
  441. //
  442. // Content-Type is multipart/byteranges; boundary=....
  443. //
  444. if (FAILED(hr = pResponse->SetHeaderByReference(
  445. HttpHeaderContentType,
  446. sm_pstrRangeContentType->QueryStr(),
  447. (USHORT)sm_pstrRangeContentType->QueryCCH())))
  448. {
  449. return hr;
  450. }
  451. //
  452. // The actual content-type of the entity to be sent with each part
  453. //
  454. if (FAILED(hr = strPartContentType.Copy("Content-Type: ")) ||
  455. FAILED(hr = strPartContentType.Append(*pstrContentType)) ||
  456. FAILED(hr = strPartContentType.Append("\r\n")))
  457. {
  458. return hr;
  459. }
  460. }
  461. //
  462. // build the response
  463. //
  464. STRA strContentRange;
  465. STRA strDelimiter;
  466. pByteRange = (HTTP_BYTE_RANGE *)bufByteRange.QueryPtr();
  467. for (DWORD i = 0; i < cRanges; i++)
  468. {
  469. liRangeOffset.QuadPart = pByteRange[i].StartingOffset.QuadPart;
  470. liRangeSize.QuadPart = pByteRange[i].Length.QuadPart;
  471. //
  472. // Build up the Content-Range header
  473. //
  474. CHAR pszSize[32];
  475. if (FAILED(hr = strContentRange.Copy("bytes ")))
  476. {
  477. return hr;
  478. }
  479. _i64toa(liRangeOffset.QuadPart, pszSize, 10);
  480. if (FAILED(hr = strContentRange.Append(pszSize)) ||
  481. FAILED(hr = strContentRange.Append("-")))
  482. {
  483. return hr;
  484. }
  485. _i64toa(liRangeOffset.QuadPart + liRangeSize.QuadPart - 1,
  486. pszSize, 10);
  487. if (FAILED(hr = strContentRange.Append(pszSize)) ||
  488. FAILED(hr = strContentRange.Append("/")))
  489. {
  490. return hr;
  491. }
  492. _i64toa(liFileSize.QuadPart, pszSize, 10);
  493. if (FAILED(hr = strContentRange.Append(pszSize)))
  494. {
  495. return hr;
  496. }
  497. if (cRanges == 1)
  498. {
  499. //
  500. // If only one chunk, send Content-Range as a header
  501. //
  502. if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange,
  503. strContentRange.QueryStr(),
  504. (USHORT)strContentRange.QueryCCH())))
  505. {
  506. return hr;
  507. }
  508. }
  509. else
  510. {
  511. if (i > 0)
  512. {
  513. // Already sent a range, add a newline
  514. if (FAILED(hr = strDelimiter.Copy("\r\n", 2)))
  515. {
  516. return hr;
  517. }
  518. }
  519. //
  520. // Add delimiter, Content-Type, Content-Range
  521. //
  522. if (FAILED(hr = strDelimiter.Append(*sm_pstrRangeMidDelimiter)) ||
  523. FAILED(hr = strDelimiter.Append(strPartContentType)) ||
  524. FAILED(hr = strDelimiter.Append(HEADER("Content-Range: "))) ||
  525. FAILED(hr = strDelimiter.Append(strContentRange)) ||
  526. FAILED(hr = strDelimiter.Append("\r\n\r\n", 4)))
  527. {
  528. return hr;
  529. }
  530. //
  531. // store the ANSI version in the BUFFER chain
  532. //
  533. DWORD bufSize = strDelimiter.QueryCCH() + 1;
  534. BUFFER_CHAIN_ITEM *pBCI = new BUFFER_CHAIN_ITEM(bufSize);
  535. if (pBCI == NULL)
  536. {
  537. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  538. }
  539. memcpy(pBCI->QueryPtr(),
  540. strDelimiter.QueryStr(),
  541. bufSize);
  542. if (!m_RangeBufferChain.AppendBuffer(pBCI))
  543. {
  544. return HRESULT_FROM_WIN32(GetLastError());
  545. }
  546. //
  547. // Now actually add this delimiter chunk
  548. //
  549. if (FAILED(hr = pResponse->AddMemoryChunkByReference(
  550. pBCI->QueryPtr(),
  551. bufSize - 1)))
  552. {
  553. return hr;
  554. }
  555. }
  556. //
  557. // Add the actual file chunk
  558. //
  559. if (pOpenFile->QueryFileBuffer() != NULL &&
  560. liRangeSize.HighPart == 0 &&
  561. liRangeOffset.HighPart == 0)
  562. {
  563. hr = pResponse->AddMemoryChunkByReference(
  564. pOpenFile->QueryFileBuffer() + liRangeOffset.LowPart,
  565. liRangeSize.LowPart);
  566. }
  567. else
  568. {
  569. hr = pResponse->AddFileHandleChunk( pOpenFile->QueryFileHandle(),
  570. liRangeOffset.QuadPart,
  571. liRangeSize.QuadPart );
  572. }
  573. if (FAILED(hr))
  574. {
  575. return hr;
  576. }
  577. }
  578. if (cRanges > 1)
  579. {
  580. //
  581. // Add the terminating delimiter
  582. //
  583. if (FAILED(hr = pResponse->AddMemoryChunkByReference(
  584. sm_pstrRangeEndDelimiter->QueryStr(),
  585. sm_pstrRangeEndDelimiter->QueryCCH())))
  586. {
  587. return hr;
  588. }
  589. }
  590. return S_OK;
  591. }