Source code of Windows XP (NT5)
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.

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