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.

828 lines
18 KiB

  1. // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  2. //
  3. // RGITER.CPP
  4. //
  5. // HTTP Range Iterator implementation.
  6. //
  7. //
  8. // Copyright 1997 Microsoft Corporation, All Rights Reserved
  9. //
  10. // Windows headers
  11. //
  12. //$HACK!
  13. //
  14. // Define _WINSOCKAPI_ to keep windows.h from including winsock.h,
  15. // whose declarations would be redefined in winsock2.h,
  16. // which is included by iisextp.h,
  17. // which we include in davimpl.h!
  18. //
  19. #define _WINSOCKAPI_
  20. #include <windows.h>
  21. #pragma warning(disable:4201) // nameless struct/union
  22. #pragma warning(disable: 4284) // operator-> to a non UDT
  23. #include <tchar.h>
  24. #include <stdio.h>
  25. #include <string.h>
  26. #include <caldbg.h>
  27. #include <sz.h>
  28. #include <davsc.h>
  29. #include <ex\autoptr.h>
  30. #include <ex\rgiter.h>
  31. // Class CRangeBase ----------------------------------------------------------
  32. //
  33. CRangeBase::~CRangeBase()
  34. {
  35. }
  36. VOID
  37. CRangeBase::CollapseUnknown()
  38. {
  39. BYTE * pb;
  40. const RGITEM * prgi;
  41. DWORD cbrgi;
  42. DWORD dwOffset;
  43. DWORD irg;
  44. // Rip through the list, collapsing as we go.
  45. //
  46. for (irg = 0, dwOffset = 0, pb = m_pbData.get();
  47. irg < m_cRGList;
  48. )
  49. {
  50. // Find the current RGITEM structure
  51. //
  52. prgi = reinterpret_cast<RGITEM *>(pb + dwOffset);
  53. cbrgi = CbRangeItem(prgi);
  54. if (RANGE_UNKNOWN == prgi->uRT)
  55. {
  56. // Slurp the remaining ranges down
  57. //
  58. memcpy (pb + dwOffset, /* current rgitem */
  59. pb + dwOffset + cbrgi, /* next rgitem */
  60. m_cbSize - dwOffset - cbrgi); /* size remaining */
  61. // Adjust our stored values
  62. //
  63. m_cbSize -= cbrgi;
  64. m_cRGList -= 1;
  65. }
  66. else
  67. {
  68. dwOffset += cbrgi;
  69. irg += 1;
  70. }
  71. }
  72. }
  73. // Fixup a range array against a given size
  74. //
  75. SCODE
  76. CRangeBase::ScFixupRanges (DWORD dwSize)
  77. {
  78. SCODE sc = W_DAV_PARTIAL_CONTENT;
  79. DWORD cUnknown = 0;
  80. // The way this works is that we iterate through all the ranges and then
  81. // fixup any of the items that need fixing up. We remember the current
  82. // position, and the current range -- this allows us to restore later as
  83. // needed.
  84. //
  85. // Store off the current item.
  86. //
  87. DWORD iCur = m_iCur;
  88. RGITEM * prgi = m_prgi;
  89. // Rewind and iterate through....
  90. //
  91. for (Rewind(); PrgiNextRange(); )
  92. {
  93. // Again, we only fixup RANGE_ROW items.
  94. //
  95. if (RANGE_ROW == m_prgi->uRT)
  96. {
  97. m_prgi->sc = S_OK;
  98. // If we have a zero count of byte/rows, we need to handle it
  99. // in a special way.
  100. //
  101. if (dwSize == 0)
  102. {
  103. // Only range of format "-n" could be zero sized.
  104. //
  105. if (!FRangePresent (m_prgi->dwrgi.dwFirst))
  106. {
  107. Assert (FRangePresent(m_prgi->dwrgi.dwLast));
  108. // Note, we don't have a way to represent NULL range.
  109. // However, we do need to have range...
  110. //
  111. m_prgi->dwrgi.dwFirst = 0;
  112. m_prgi->dwrgi.dwLast = static_cast<DWORD>(RANGE_NOT_PRESENT);
  113. }
  114. }
  115. else
  116. {
  117. // If we don't have a last count...
  118. //
  119. if (!FRangePresent (m_prgi->dwrgi.dwLast))
  120. {
  121. // We must have checked the syntax already
  122. //
  123. Assert (FRangePresent(m_prgi->dwrgi.dwFirst));
  124. // We have first byte to send, calculate last byte from size
  125. // We need to send from first byte to end.
  126. //
  127. m_prgi->dwrgi.dwFirst = m_prgi->dwrgi.dwFirst;
  128. m_prgi->dwrgi.dwLast = dwSize - 1;
  129. }
  130. //
  131. // ... or a last count without a first count...
  132. //
  133. else if (!FRangePresent(m_prgi->dwrgi.dwFirst))
  134. {
  135. Assert (FRangePresent(m_prgi->dwrgi.dwLast));
  136. // We have the last count dwLast, which means we need
  137. // to send the last dwLast bytes. Calculate the first
  138. // count from the size. If they specify a size greater
  139. // then the size of entity, then use the size of the
  140. // entire entity
  141. //
  142. DWORD dwLast = min(m_prgi->dwrgi.dwLast, dwSize);
  143. m_prgi->dwrgi.dwFirst = dwSize - dwLast;
  144. m_prgi->dwrgi.dwLast = dwSize - 1;
  145. }
  146. //
  147. // ... or both counts are present...
  148. //
  149. else
  150. {
  151. // If they specify a last count that is beyond the actual
  152. // count.
  153. //
  154. m_prgi->dwrgi.dwLast = min(m_prgi->dwrgi.dwLast, dwSize - 1);
  155. }
  156. // Now perform one additional validity check. If the start
  157. // falls after the end, the range is not statisfiable.
  158. //
  159. if (m_prgi->dwrgi.dwLast < m_prgi->dwrgi.dwFirst)
  160. {
  161. // In this case, we want to collapse this item out of the
  162. // list so that we can handle the ranges properly in the
  163. // IIS-side of range header handling.
  164. //
  165. // Remember that we have this handling to do, and deal
  166. // with it at a later time.
  167. //
  168. m_prgi->uRT = RANGE_UNKNOWN;
  169. m_prgi->sc = E_DAV_RANGE_NOT_SATISFIABLE;
  170. cUnknown += 1;
  171. }
  172. }
  173. }
  174. }
  175. // If we did not find any valid ranges
  176. //
  177. if (cUnknown == m_cRGList)
  178. {
  179. // None of the ranges were satisfiable for the entity size.
  180. //
  181. sc = E_DAV_RANGE_NOT_SATISFIABLE;
  182. }
  183. // Now is the time when we want to collapse out any unknown ranges
  184. // out of the list.
  185. //
  186. if (0 != cUnknown)
  187. {
  188. // This is important handling for the case of byte-ranges where
  189. // there is only one resulting range that is applicable.
  190. //
  191. CollapseUnknown();
  192. }
  193. // Restore the current position and return
  194. //
  195. m_iCur = iCur;
  196. m_prgi = prgi;
  197. return sc;
  198. }
  199. const RGITEM *
  200. CRangeBase::PrgiNextRange()
  201. {
  202. const RGITEM * prgi = NULL;
  203. if (FMoreRanges())
  204. {
  205. UINT cb = 0;
  206. BYTE * pb = NULL;
  207. // If the main pointer is NULL, then we know that we have not
  208. // setup for any ranges yet.
  209. //
  210. if (NULL == m_prgi)
  211. {
  212. pb = reinterpret_cast<BYTE*>(m_pbData.get());
  213. }
  214. else
  215. {
  216. // Otherwise, we need to adjust our position based
  217. // on the size of the current item
  218. //
  219. // Find the size of the item
  220. //
  221. cb = CbRangeItem (m_prgi);
  222. pb = reinterpret_cast<BYTE*>(m_prgi);
  223. }
  224. // Scoot forward
  225. //
  226. m_prgi = reinterpret_cast<RGITEM*>(pb + cb);
  227. m_iCur += 1;
  228. // Ensure the boundry
  229. //
  230. Assert (reinterpret_cast<BYTE*>(m_prgi) <= (m_pbData.get() + m_cbSize));
  231. prgi = m_prgi;
  232. }
  233. return prgi;
  234. }
  235. // Class CRangeParser --------------------------------------------------------
  236. //
  237. CRangeParser::~CRangeParser()
  238. {
  239. }
  240. // Takes a range header and builds an array of ranges. Performs syntax
  241. // checking.
  242. //
  243. // S_OK is returned if no syntax error, otherwise, S_FALSE is returned
  244. //
  245. SCODE
  246. CRangeParser::ScParseRangeHdr (LPCWSTR pwszRgHeader, LPCWSTR pwszRangeUnit)
  247. {
  248. LPCWSTR pwsz, pwszEnd;
  249. SCODE sc = S_OK;
  250. BOOL bFirst = FALSE, bLast = FALSE;
  251. DWORD dwFirst = 0, dwLast = 0;
  252. DWORD cRanges = 0;
  253. Assert (pwszRgHeader);
  254. pwsz = pwszRgHeader;
  255. // The first word has to be the range unit, either gc_wszBytes
  256. // or gc_wszRows
  257. //
  258. Assert (!_wcsnicmp (pwszRangeUnit, gc_wszBytes, wcslen(gc_wszBytes)) ||
  259. !_wcsnicmp (pwszRangeUnit, gc_wszRows, wcslen(gc_wszRows)));
  260. if (_wcsnicmp(pwsz, pwszRangeUnit, wcslen(pwszRangeUnit)))
  261. {
  262. // OK, the header did not start with range unit
  263. //
  264. sc = E_INVALIDARG;
  265. goto ret;
  266. }
  267. // Move past the range unit
  268. //
  269. pwsz = pwsz + wcslen(pwszRangeUnit);
  270. // Skip any whitespace
  271. //
  272. pwsz = _wcsspnp (pwsz, gc_wszWS);
  273. if (!pwsz)
  274. {
  275. // OK, the header does not have any ranges
  276. //
  277. sc = E_INVALIDARG;
  278. goto ret;
  279. }
  280. // We need an = immediately after the range unit
  281. //
  282. if (gc_wchEquals != *pwsz++)
  283. {
  284. // OK, improper format
  285. //
  286. sc = E_INVALIDARG;
  287. goto ret;
  288. }
  289. // Count the number of comma separated ranges we have
  290. // While this algorithm results in m_cRGList being equal to one more
  291. // than the number of commas, that is exactly what we want. The number
  292. // of ranges is always less than or equal to one more than the number of
  293. // commas.
  294. //
  295. while (pwsz)
  296. {
  297. // Find a comma
  298. //
  299. pwsz = wcschr(pwsz, gc_wchComma);
  300. // If we have a comma, move past it
  301. //
  302. if (pwsz)
  303. pwsz++;
  304. // Increment the count
  305. //
  306. cRanges += 1;
  307. }
  308. // Parse the header to find the byte ranges
  309. //
  310. // Seek past the byte unit
  311. //
  312. pwsz = wcschr(pwszRgHeader, gc_wchEquals);
  313. // We already checked for an =, so assert
  314. //
  315. Assert (pwsz);
  316. pwsz++;
  317. // Any characters in our byte range except the characters 0..9,-,comma
  318. // and whitespace are illegal. We check to see if we have any illegal characters
  319. // using the function _wcsspnp(string1, string2) which finds the first character
  320. // in string1 that does not belong to the set of characters in string2
  321. //
  322. pwszEnd = _wcsspnp(pwsz, gc_wszByteRangeAlphabet);
  323. if (pwszEnd)
  324. {
  325. // We found an illegal character
  326. //
  327. sc = E_INVALIDARG;
  328. goto ret;
  329. }
  330. // Skip any whitespace and separators
  331. //
  332. pwsz = _wcsspnp (pwsz, gc_wszSeparator);
  333. if (!pwsz)
  334. {
  335. // OK, the header does not have any ranges
  336. //
  337. sc = E_INVALIDARG;
  338. goto ret;
  339. }
  340. // Create the required storage
  341. //
  342. m_cRGList = 0;
  343. m_cbSize = cRanges * sizeof(RGITEM);
  344. m_pbData = static_cast<BYTE*>(ExAlloc(m_cbSize));
  345. m_prgi = reinterpret_cast<RGITEM*>(m_pbData.get());
  346. // Make sure the allocation succeeds
  347. //
  348. if (NULL == m_prgi)
  349. {
  350. sc = E_OUTOFMEMORY;
  351. goto ret;
  352. }
  353. // Iterate through the byte ranges
  354. //
  355. while (*pwsz != NULL)
  356. {
  357. pwszEnd = _wcsspnp (pwsz, gc_wszDigits);
  358. // Do we have a first byte?
  359. //
  360. if (!pwszEnd)
  361. {
  362. // This is illegal. We cannot just have a first byte and
  363. // nothing after it
  364. //
  365. sc = E_INVALIDARG;
  366. goto ret;
  367. }
  368. else if (pwsz != pwszEnd)
  369. {
  370. dwFirst = _wtoi(pwsz);
  371. bFirst = TRUE;
  372. // Seek past the end of the first byte
  373. //
  374. pwsz = pwszEnd;
  375. }
  376. // Now we should find the -
  377. //
  378. if (*pwsz != gc_wchDash)
  379. {
  380. sc = E_INVALIDARG;
  381. goto ret;
  382. }
  383. pwsz++;
  384. // If we aren't at the end of the string, look for the last byte
  385. //
  386. if (*pwsz != NULL)
  387. {
  388. pwszEnd = _wcsspnp(pwsz, gc_wszDigits);
  389. // Do we have a last byte?
  390. //
  391. if (pwsz != pwszEnd)
  392. {
  393. dwLast = _wtoi(pwsz);
  394. bLast = TRUE;
  395. }
  396. // Update psz to the end of the current range
  397. //
  398. if (!pwszEnd)
  399. {
  400. // We must be at the end of the header. Update psz
  401. //
  402. pwsz = pwsz + wcslen(pwsz);
  403. }
  404. else
  405. {
  406. pwsz = pwszEnd;
  407. }
  408. }
  409. // It's a syntax error if we don't have both first and last range
  410. // or the last is less than the first
  411. //
  412. if ((!bFirst && !bLast) ||
  413. (bFirst && bLast && (dwLast < dwFirst)))
  414. {
  415. sc = E_INVALIDARG;
  416. goto ret;
  417. }
  418. // We are done parsing the byte/row range, now save it.
  419. //
  420. Assert (m_cRGList < cRanges);
  421. m_prgi[m_cRGList].uRT = RANGE_ROW;
  422. m_prgi[m_cRGList].sc = S_OK;
  423. m_prgi[m_cRGList].dwrgi.dwFirst = bFirst ? dwFirst : RANGE_NOT_PRESENT;
  424. m_prgi[m_cRGList].dwrgi.dwLast = bLast ? dwLast : RANGE_NOT_PRESENT;
  425. m_cRGList += 1;
  426. // Update variables
  427. //
  428. bFirst = bLast = FALSE;
  429. dwFirst = dwLast = 0;
  430. // Skip any whitespace
  431. //
  432. pwsz = _wcsspnp (pwsz, gc_wszWS);
  433. if (!pwsz)
  434. {
  435. // OK, we don't have anything beyond whitespace, we are at the end
  436. //
  437. goto ret;
  438. }
  439. else if (*pwsz != gc_wchComma)
  440. {
  441. // The first non-whitespace character has to be a separator(comma)
  442. //
  443. sc = E_INVALIDARG;
  444. goto ret;
  445. }
  446. // Now that we found the first comma, skip any number of subsequent
  447. // commas and whitespace
  448. //
  449. pwsz = _wcsspnp (pwsz, gc_wszSeparator);
  450. if (!pwsz)
  451. {
  452. // OK, we don't have anything beyond separator, we are at the end
  453. //
  454. goto ret;
  455. }
  456. }
  457. ret:
  458. if (FAILED (sc))
  459. {
  460. // Free up our storage
  461. //
  462. m_cbSize = 0;
  463. m_cRGList = 0;
  464. m_pbData.clear();
  465. Rewind();
  466. }
  467. return sc;
  468. }
  469. // Don't use FAILED() macros on this return code! You'll miss the details!
  470. //
  471. // Takes a range header and builds an array of ranges. Performs syntax
  472. // checking and validation of the ranges against the entity size.
  473. // Returns an SCODE, but be careful! These SCODEs are meant to be
  474. // mapped to HSCs at a higher level.
  475. //
  476. // E_INVALIDARG means syntax error
  477. //
  478. // E_DAV_RANGE_NOT_SATISFIABLE if none of the ranges were valid
  479. // for the entity size passed in.
  480. //
  481. // W_DAV_PARTIAL_CONTENT if there was at least one valid range.
  482. //
  483. // This function does NOT normally return S_OK. Only one of the above!
  484. //
  485. SCODE
  486. CRangeParser::ScParseByteRangeHdr (LPCWSTR pwszRgHeader, DWORD dwSize)
  487. {
  488. SCODE sc = S_OK;
  489. Assert(pwszRgHeader);
  490. // Parses the ranges header and builds an array of the ranges
  491. //
  492. sc = ScParseRangeHdr (pwszRgHeader, gc_wszBytes);
  493. if (FAILED (sc))
  494. goto ret;
  495. // Fixup the ranges as needed
  496. //
  497. sc = ScFixupRanges (dwSize);
  498. Assert ((sc == W_DAV_PARTIAL_CONTENT) ||
  499. (sc == E_DAV_RANGE_NOT_SATISFIABLE));
  500. ret:
  501. return sc;
  502. }
  503. // Class CRangeIter ----------------------------------------------------------
  504. //
  505. CRangeIter::~CRangeIter()
  506. {
  507. }
  508. SCODE
  509. CRangeIter::ScInit (ULONG cRGList, const RGITEM * prgRGList, ULONG cbSize)
  510. {
  511. SCODE sc = S_OK;
  512. // The object must not have been initialized before
  513. //
  514. Assert (!m_pbData.get() && (0 == m_cRGList));
  515. // Make sure we are given good bits...
  516. //
  517. Assert (cRGList);
  518. Assert (prgRGList);
  519. Assert (cbSize);
  520. // Duplicate the RGITEM array
  521. //
  522. m_pbData = static_cast<BYTE*>(ExAlloc(cbSize));
  523. if (!m_pbData.get())
  524. {
  525. sc = E_OUTOFMEMORY;
  526. goto ret;
  527. }
  528. CopyMemory (m_pbData.get(), prgRGList, cbSize);
  529. // Remember the count and size
  530. //
  531. m_cRGList = cRGList;
  532. m_cbSize = cbSize;
  533. // Rewind to the beginning of the ranges
  534. //
  535. Rewind();
  536. ret:
  537. return sc;
  538. }
  539. // Range Parsing -------------------------------------------------------------
  540. //
  541. SCODE
  542. ScParseOneWideRange (LPCWSTR pwsz, DWORD * pdwStart, DWORD * pdwEnd)
  543. {
  544. BOOL fEnd = FALSE;
  545. BOOL fStart = FALSE;
  546. DWORD dwEnd = static_cast<DWORD>(RANGE_NOT_PRESENT);
  547. DWORD dwStart = static_cast<DWORD>(RANGE_NOT_PRESENT);
  548. LPCWSTR pwszEnd;
  549. SCODE sc = S_OK;
  550. // A quick note about the format here...
  551. //
  552. // row_range= digit* '-' digit*
  553. // digit= [0-9]
  554. //
  555. // So, the first thing we need to check is if there is a leading set of
  556. // digits to indicate a starting point.
  557. //
  558. pwszEnd = _wcsspnp (pwsz, gc_wszDigits);
  559. // If the return value is NULL, or points to a NULL, then we have an
  560. // invalid range. It is not valid to simply have a set of digits
  561. //
  562. if ((NULL == pwszEnd) || (0 == *pwszEnd))
  563. {
  564. sc = E_INVALIDARG;
  565. goto ret;
  566. }
  567. //
  568. // Else if the current position and the end refer to the same
  569. // character, then there is no starting range.
  570. //
  571. else if (pwsz != pwszEnd)
  572. {
  573. dwStart = wcstoul (pwsz, NULL, 10 /* always base 10 */);
  574. pwsz = pwszEnd;
  575. fStart = TRUE;
  576. }
  577. // Regardless, at this point we should have a single '-' character
  578. //
  579. if (L'-' != *pwsz++)
  580. {
  581. sc = E_INVALIDARG;
  582. goto ret;
  583. }
  584. // Any remaining characters should be the end of the range
  585. //
  586. if (0 != *pwsz)
  587. {
  588. pwszEnd = _wcsspnp (pwsz, gc_wszDigits);
  589. // Here we expect that the return value is not the same as
  590. // the initial pointer
  591. //
  592. if ((NULL != pwszEnd) && (0 != pwszEnd))
  593. {
  594. sc = E_INVALIDARG;
  595. goto ret;
  596. }
  597. dwEnd = wcstoul (pwsz, NULL, 10 /* always base 10 */);
  598. fEnd = TRUE;
  599. }
  600. // Can't have both end-points as non-existant ranges
  601. //
  602. if ((!fStart && !fEnd) ||
  603. (fStart && fEnd && (dwEnd < dwStart)))
  604. {
  605. sc = E_INVALIDARG;
  606. goto ret;
  607. }
  608. ret:
  609. *pdwStart = dwStart;
  610. *pdwEnd = dwEnd;
  611. return sc;
  612. }
  613. // ScGenerateContentRange() --------------------------------------------------
  614. //
  615. enum { BUFFER_INITIAL_SIZE = 512 };
  616. // ScGenerateContentRange
  617. //
  618. // Helper function to build the content-range header
  619. //
  620. // If ulTotal is RGITER_TOTAL_UNKNOWN ((ULONG)-1), then we give back "total=*".
  621. // This is needed for REPL, because our store api doesn't tell us how many possible
  622. // rows there are up front.
  623. //
  624. SCODE ScGenerateContentRange (
  625. /* [in] */ LPCSTR pszRangeUnit,
  626. /* [in] */ const RGITEM * prgRGList,
  627. /* [in] */ ULONG cRanges,
  628. /* [in] */ ULONG cbRanges,
  629. /* [in] */ ULONG ulTotal,
  630. /* [out] */ LPSTR *ppszContentRange)
  631. {
  632. auto_heap_ptr<CHAR> pszCR;
  633. BOOL fMultipleRanges = FALSE;
  634. CRangeIter cri;
  635. SCODE sc = E_INVALIDARG;
  636. ULONG cb = 0;
  637. ULONG cbSize = BUFFER_INITIAL_SIZE;
  638. // We must have something to emit
  639. //
  640. Assert (ppszContentRange);
  641. Assert (cRanges);
  642. sc = cri.ScInit (cRanges, prgRGList, cbRanges);
  643. if (FAILED (sc))
  644. goto ret;
  645. // Allocate the space for the header
  646. //
  647. pszCR = static_cast<LPSTR>(ExAlloc (cbSize));
  648. if (!pszCR.get())
  649. {
  650. sc = E_OUTOFMEMORY;
  651. goto ret;
  652. }
  653. // Setup the leading range units, etc...
  654. //
  655. strcpy (pszCR.get() + cb, pszRangeUnit);
  656. cb += static_cast<ULONG>(strlen(pszRangeUnit));
  657. // Stuff in a leading space
  658. //
  659. pszCR.get()[cb++] = ' ';
  660. // Now iterate through the ranges to add in
  661. // each range.
  662. //
  663. while (NULL != (prgRGList = cri.PrgiNextRange()))
  664. {
  665. // If the range is unknown, then it is a range
  666. // that was not processed on the store side.
  667. //
  668. if (RANGE_UNKNOWN == prgRGList->uRT)
  669. continue;
  670. // First off, make sure there is plenty of room
  671. //
  672. if (cb > cbSize - 50)
  673. {
  674. // Realloc the buffer
  675. //
  676. cbSize = cbSize + BUFFER_INITIAL_SIZE;
  677. pszCR.realloc (cbSize);
  678. // It's possible that the allocation fails
  679. //
  680. if (!pszCR.get())
  681. goto ret;
  682. }
  683. // Now that we know we have space...
  684. // If this is a subsequent range to the initial
  685. // one, add in a comma.
  686. //
  687. if (fMultipleRanges)
  688. {
  689. // Stuff in a comma and a space
  690. //
  691. pszCR.get()[cb++] = ',';
  692. pszCR.get()[cb++] = ' ';
  693. }
  694. if (RANGE_ROW == prgRGList->uRT)
  695. {
  696. // 50 is a safe numder of bytes to hold the last range and
  697. // "total = <size>"
  698. //
  699. // Append the next range
  700. //
  701. cb += sprintf (pszCR.get() + cb,
  702. "%u-%u",
  703. prgRGList->dwrgi.dwFirst,
  704. prgRGList->dwrgi.dwLast);
  705. }
  706. else
  707. {
  708. // For all non-row ranges, we really don't know the ordinals
  709. // of the rows up front. We only find that info out when the
  710. // rows are actually queried. This happens long after the
  711. // content-range header is constructed, so we stuff in a place
  712. // holder for these ranges.
  713. //
  714. pszCR.get()[cb++] = '*';
  715. }
  716. fMultipleRanges = TRUE;
  717. }
  718. // Now it's time to append the "total=<size>"
  719. // Handle the special case of RGITER_TOTAL_UNKNOWN -- give "total=*".
  720. //
  721. if (RANGE_TOTAL_UNKNOWN == ulTotal)
  722. {
  723. const char rgTotalStar[] = "; total=*";
  724. memcpy (pszCR.get() + cb, rgTotalStar, CElems(rgTotalStar));
  725. }
  726. else
  727. {
  728. sprintf(pszCR.get() + cb, "; total=%u", ulTotal);
  729. }
  730. // Pass the buffer back
  731. //
  732. *ppszContentRange = pszCR.relinquish();
  733. sc = S_OK;
  734. ret:
  735. return sc;
  736. }