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
828 lines
18 KiB
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
//
|
|
// RGITER.CPP
|
|
//
|
|
// HTTP Range Iterator implementation.
|
|
//
|
|
//
|
|
// Copyright 1997 Microsoft Corporation, All Rights Reserved
|
|
//
|
|
|
|
// Windows headers
|
|
//
|
|
//$HACK!
|
|
//
|
|
// Define _WINSOCKAPI_ to keep windows.h from including winsock.h,
|
|
// whose declarations would be redefined in winsock2.h,
|
|
// which is included by iisextp.h,
|
|
// which we include in davimpl.h!
|
|
//
|
|
#define _WINSOCKAPI_
|
|
#include <windows.h>
|
|
|
|
#pragma warning(disable:4201) // nameless struct/union
|
|
#pragma warning(disable: 4284) // operator-> to a non UDT
|
|
|
|
#include <tchar.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <caldbg.h>
|
|
#include <sz.h>
|
|
#include <davsc.h>
|
|
#include <ex\autoptr.h>
|
|
#include <ex\rgiter.h>
|
|
|
|
// Class CRangeBase ----------------------------------------------------------
|
|
//
|
|
CRangeBase::~CRangeBase()
|
|
{
|
|
}
|
|
|
|
VOID
|
|
CRangeBase::CollapseUnknown()
|
|
{
|
|
BYTE * pb;
|
|
const RGITEM * prgi;
|
|
DWORD cbrgi;
|
|
DWORD dwOffset;
|
|
DWORD irg;
|
|
|
|
// Rip through the list, collapsing as we go.
|
|
//
|
|
for (irg = 0, dwOffset = 0, pb = m_pbData.get();
|
|
irg < m_cRGList;
|
|
)
|
|
{
|
|
// Find the current RGITEM structure
|
|
//
|
|
prgi = reinterpret_cast<RGITEM *>(pb + dwOffset);
|
|
cbrgi = CbRangeItem(prgi);
|
|
|
|
if (RANGE_UNKNOWN == prgi->uRT)
|
|
{
|
|
// Slurp the remaining ranges down
|
|
//
|
|
memcpy (pb + dwOffset, /* current rgitem */
|
|
pb + dwOffset + cbrgi, /* next rgitem */
|
|
m_cbSize - dwOffset - cbrgi); /* size remaining */
|
|
|
|
// Adjust our stored values
|
|
//
|
|
m_cbSize -= cbrgi;
|
|
m_cRGList -= 1;
|
|
}
|
|
else
|
|
{
|
|
dwOffset += cbrgi;
|
|
irg += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fixup a range array against a given size
|
|
//
|
|
SCODE
|
|
CRangeBase::ScFixupRanges (DWORD dwSize)
|
|
{
|
|
SCODE sc = W_DAV_PARTIAL_CONTENT;
|
|
DWORD cUnknown = 0;
|
|
|
|
// The way this works is that we iterate through all the ranges and then
|
|
// fixup any of the items that need fixing up. We remember the current
|
|
// position, and the current range -- this allows us to restore later as
|
|
// needed.
|
|
//
|
|
// Store off the current item.
|
|
//
|
|
DWORD iCur = m_iCur;
|
|
RGITEM * prgi = m_prgi;
|
|
|
|
// Rewind and iterate through....
|
|
//
|
|
for (Rewind(); PrgiNextRange(); )
|
|
{
|
|
// Again, we only fixup RANGE_ROW items.
|
|
//
|
|
if (RANGE_ROW == m_prgi->uRT)
|
|
{
|
|
m_prgi->sc = S_OK;
|
|
|
|
// If we have a zero count of byte/rows, we need to handle it
|
|
// in a special way.
|
|
//
|
|
if (dwSize == 0)
|
|
{
|
|
// Only range of format "-n" could be zero sized.
|
|
//
|
|
if (!FRangePresent (m_prgi->dwrgi.dwFirst))
|
|
{
|
|
Assert (FRangePresent(m_prgi->dwrgi.dwLast));
|
|
|
|
// Note, we don't have a way to represent NULL range.
|
|
// However, we do need to have range...
|
|
//
|
|
m_prgi->dwrgi.dwFirst = 0;
|
|
m_prgi->dwrgi.dwLast = static_cast<DWORD>(RANGE_NOT_PRESENT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we don't have a last count...
|
|
//
|
|
if (!FRangePresent (m_prgi->dwrgi.dwLast))
|
|
{
|
|
// We must have checked the syntax already
|
|
//
|
|
Assert (FRangePresent(m_prgi->dwrgi.dwFirst));
|
|
|
|
// We have first byte to send, calculate last byte from size
|
|
// We need to send from first byte to end.
|
|
//
|
|
m_prgi->dwrgi.dwFirst = m_prgi->dwrgi.dwFirst;
|
|
m_prgi->dwrgi.dwLast = dwSize - 1;
|
|
}
|
|
//
|
|
// ... or a last count without a first count...
|
|
//
|
|
else if (!FRangePresent(m_prgi->dwrgi.dwFirst))
|
|
{
|
|
Assert (FRangePresent(m_prgi->dwrgi.dwLast));
|
|
|
|
// We have the last count dwLast, which means we need
|
|
// to send the last dwLast bytes. Calculate the first
|
|
// count from the size. If they specify a size greater
|
|
// then the size of entity, then use the size of the
|
|
// entire entity
|
|
//
|
|
DWORD dwLast = min(m_prgi->dwrgi.dwLast, dwSize);
|
|
m_prgi->dwrgi.dwFirst = dwSize - dwLast;
|
|
m_prgi->dwrgi.dwLast = dwSize - 1;
|
|
}
|
|
//
|
|
// ... or both counts are present...
|
|
//
|
|
else
|
|
{
|
|
// If they specify a last count that is beyond the actual
|
|
// count.
|
|
//
|
|
m_prgi->dwrgi.dwLast = min(m_prgi->dwrgi.dwLast, dwSize - 1);
|
|
}
|
|
|
|
// Now perform one additional validity check. If the start
|
|
// falls after the end, the range is not statisfiable.
|
|
//
|
|
if (m_prgi->dwrgi.dwLast < m_prgi->dwrgi.dwFirst)
|
|
{
|
|
// In this case, we want to collapse this item out of the
|
|
// list so that we can handle the ranges properly in the
|
|
// IIS-side of range header handling.
|
|
//
|
|
// Remember that we have this handling to do, and deal
|
|
// with it at a later time.
|
|
//
|
|
m_prgi->uRT = RANGE_UNKNOWN;
|
|
m_prgi->sc = E_DAV_RANGE_NOT_SATISFIABLE;
|
|
cUnknown += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we did not find any valid ranges
|
|
//
|
|
if (cUnknown == m_cRGList)
|
|
{
|
|
// None of the ranges were satisfiable for the entity size.
|
|
//
|
|
sc = E_DAV_RANGE_NOT_SATISFIABLE;
|
|
}
|
|
|
|
// Now is the time when we want to collapse out any unknown ranges
|
|
// out of the list.
|
|
//
|
|
if (0 != cUnknown)
|
|
{
|
|
// This is important handling for the case of byte-ranges where
|
|
// there is only one resulting range that is applicable.
|
|
//
|
|
CollapseUnknown();
|
|
}
|
|
|
|
// Restore the current position and return
|
|
//
|
|
m_iCur = iCur;
|
|
m_prgi = prgi;
|
|
return sc;
|
|
}
|
|
|
|
const RGITEM *
|
|
CRangeBase::PrgiNextRange()
|
|
{
|
|
const RGITEM * prgi = NULL;
|
|
|
|
if (FMoreRanges())
|
|
{
|
|
UINT cb = 0;
|
|
BYTE * pb = NULL;
|
|
|
|
// If the main pointer is NULL, then we know that we have not
|
|
// setup for any ranges yet.
|
|
//
|
|
if (NULL == m_prgi)
|
|
{
|
|
pb = reinterpret_cast<BYTE*>(m_pbData.get());
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, we need to adjust our position based
|
|
// on the size of the current item
|
|
//
|
|
// Find the size of the item
|
|
//
|
|
cb = CbRangeItem (m_prgi);
|
|
pb = reinterpret_cast<BYTE*>(m_prgi);
|
|
}
|
|
|
|
// Scoot forward
|
|
//
|
|
m_prgi = reinterpret_cast<RGITEM*>(pb + cb);
|
|
m_iCur += 1;
|
|
|
|
// Ensure the boundry
|
|
//
|
|
Assert (reinterpret_cast<BYTE*>(m_prgi) <= (m_pbData.get() + m_cbSize));
|
|
prgi = m_prgi;
|
|
}
|
|
return prgi;
|
|
}
|
|
|
|
// Class CRangeParser --------------------------------------------------------
|
|
//
|
|
CRangeParser::~CRangeParser()
|
|
{
|
|
}
|
|
|
|
// Takes a range header and builds an array of ranges. Performs syntax
|
|
// checking.
|
|
//
|
|
// S_OK is returned if no syntax error, otherwise, S_FALSE is returned
|
|
//
|
|
SCODE
|
|
CRangeParser::ScParseRangeHdr (LPCWSTR pwszRgHeader, LPCWSTR pwszRangeUnit)
|
|
{
|
|
LPCWSTR pwsz, pwszEnd;
|
|
SCODE sc = S_OK;
|
|
BOOL bFirst = FALSE, bLast = FALSE;
|
|
DWORD dwFirst = 0, dwLast = 0;
|
|
DWORD cRanges = 0;
|
|
|
|
Assert (pwszRgHeader);
|
|
pwsz = pwszRgHeader;
|
|
|
|
// The first word has to be the range unit, either gc_wszBytes
|
|
// or gc_wszRows
|
|
//
|
|
Assert (!_wcsnicmp (pwszRangeUnit, gc_wszBytes, wcslen(gc_wszBytes)) ||
|
|
!_wcsnicmp (pwszRangeUnit, gc_wszRows, wcslen(gc_wszRows)));
|
|
if (_wcsnicmp(pwsz, pwszRangeUnit, wcslen(pwszRangeUnit)))
|
|
{
|
|
// OK, the header did not start with range unit
|
|
//
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// Move past the range unit
|
|
//
|
|
pwsz = pwsz + wcslen(pwszRangeUnit);
|
|
|
|
// Skip any whitespace
|
|
//
|
|
pwsz = _wcsspnp (pwsz, gc_wszWS);
|
|
if (!pwsz)
|
|
{
|
|
// OK, the header does not have any ranges
|
|
//
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// We need an = immediately after the range unit
|
|
//
|
|
if (gc_wchEquals != *pwsz++)
|
|
{
|
|
// OK, improper format
|
|
//
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// Count the number of comma separated ranges we have
|
|
// While this algorithm results in m_cRGList being equal to one more
|
|
// than the number of commas, that is exactly what we want. The number
|
|
// of ranges is always less than or equal to one more than the number of
|
|
// commas.
|
|
//
|
|
while (pwsz)
|
|
{
|
|
// Find a comma
|
|
//
|
|
pwsz = wcschr(pwsz, gc_wchComma);
|
|
|
|
// If we have a comma, move past it
|
|
//
|
|
if (pwsz)
|
|
pwsz++;
|
|
|
|
// Increment the count
|
|
//
|
|
cRanges += 1;
|
|
}
|
|
|
|
// Parse the header to find the byte ranges
|
|
//
|
|
// Seek past the byte unit
|
|
//
|
|
pwsz = wcschr(pwszRgHeader, gc_wchEquals);
|
|
|
|
// We already checked for an =, so assert
|
|
//
|
|
Assert (pwsz);
|
|
pwsz++;
|
|
|
|
// Any characters in our byte range except the characters 0..9,-,comma
|
|
// and whitespace are illegal. We check to see if we have any illegal characters
|
|
// using the function _wcsspnp(string1, string2) which finds the first character
|
|
// in string1 that does not belong to the set of characters in string2
|
|
//
|
|
pwszEnd = _wcsspnp(pwsz, gc_wszByteRangeAlphabet);
|
|
if (pwszEnd)
|
|
{
|
|
// We found an illegal character
|
|
//
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// Skip any whitespace and separators
|
|
//
|
|
pwsz = _wcsspnp (pwsz, gc_wszSeparator);
|
|
if (!pwsz)
|
|
{
|
|
// OK, the header does not have any ranges
|
|
//
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// Create the required storage
|
|
//
|
|
m_cRGList = 0;
|
|
m_cbSize = cRanges * sizeof(RGITEM);
|
|
m_pbData = static_cast<BYTE*>(ExAlloc(m_cbSize));
|
|
m_prgi = reinterpret_cast<RGITEM*>(m_pbData.get());
|
|
|
|
// Make sure the allocation succeeds
|
|
//
|
|
if (NULL == m_prgi)
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
|
|
// Iterate through the byte ranges
|
|
//
|
|
while (*pwsz != NULL)
|
|
{
|
|
pwszEnd = _wcsspnp (pwsz, gc_wszDigits);
|
|
|
|
// Do we have a first byte?
|
|
//
|
|
if (!pwszEnd)
|
|
{
|
|
// This is illegal. We cannot just have a first byte and
|
|
// nothing after it
|
|
//
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
else if (pwsz != pwszEnd)
|
|
{
|
|
dwFirst = _wtoi(pwsz);
|
|
bFirst = TRUE;
|
|
|
|
// Seek past the end of the first byte
|
|
//
|
|
pwsz = pwszEnd;
|
|
}
|
|
|
|
// Now we should find the -
|
|
//
|
|
if (*pwsz != gc_wchDash)
|
|
{
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
pwsz++;
|
|
|
|
// If we aren't at the end of the string, look for the last byte
|
|
//
|
|
if (*pwsz != NULL)
|
|
{
|
|
pwszEnd = _wcsspnp(pwsz, gc_wszDigits);
|
|
|
|
// Do we have a last byte?
|
|
//
|
|
if (pwsz != pwszEnd)
|
|
{
|
|
dwLast = _wtoi(pwsz);
|
|
bLast = TRUE;
|
|
}
|
|
|
|
// Update psz to the end of the current range
|
|
//
|
|
if (!pwszEnd)
|
|
{
|
|
// We must be at the end of the header. Update psz
|
|
//
|
|
pwsz = pwsz + wcslen(pwsz);
|
|
}
|
|
else
|
|
{
|
|
pwsz = pwszEnd;
|
|
}
|
|
}
|
|
|
|
// It's a syntax error if we don't have both first and last range
|
|
// or the last is less than the first
|
|
//
|
|
if ((!bFirst && !bLast) ||
|
|
(bFirst && bLast && (dwLast < dwFirst)))
|
|
{
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// We are done parsing the byte/row range, now save it.
|
|
//
|
|
Assert (m_cRGList < cRanges);
|
|
m_prgi[m_cRGList].uRT = RANGE_ROW;
|
|
m_prgi[m_cRGList].sc = S_OK;
|
|
m_prgi[m_cRGList].dwrgi.dwFirst = bFirst ? dwFirst : RANGE_NOT_PRESENT;
|
|
m_prgi[m_cRGList].dwrgi.dwLast = bLast ? dwLast : RANGE_NOT_PRESENT;
|
|
m_cRGList += 1;
|
|
|
|
// Update variables
|
|
//
|
|
bFirst = bLast = FALSE;
|
|
dwFirst = dwLast = 0;
|
|
|
|
// Skip any whitespace
|
|
//
|
|
pwsz = _wcsspnp (pwsz, gc_wszWS);
|
|
if (!pwsz)
|
|
{
|
|
// OK, we don't have anything beyond whitespace, we are at the end
|
|
//
|
|
goto ret;
|
|
}
|
|
else if (*pwsz != gc_wchComma)
|
|
{
|
|
// The first non-whitespace character has to be a separator(comma)
|
|
//
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// Now that we found the first comma, skip any number of subsequent
|
|
// commas and whitespace
|
|
//
|
|
pwsz = _wcsspnp (pwsz, gc_wszSeparator);
|
|
if (!pwsz)
|
|
{
|
|
// OK, we don't have anything beyond separator, we are at the end
|
|
//
|
|
goto ret;
|
|
}
|
|
}
|
|
|
|
ret:
|
|
|
|
if (FAILED (sc))
|
|
{
|
|
// Free up our storage
|
|
//
|
|
m_cbSize = 0;
|
|
m_cRGList = 0;
|
|
m_pbData.clear();
|
|
Rewind();
|
|
}
|
|
return sc;
|
|
}
|
|
|
|
// Don't use FAILED() macros on this return code! You'll miss the details!
|
|
//
|
|
// Takes a range header and builds an array of ranges. Performs syntax
|
|
// checking and validation of the ranges against the entity size.
|
|
// Returns an SCODE, but be careful! These SCODEs are meant to be
|
|
// mapped to HSCs at a higher level.
|
|
//
|
|
// E_INVALIDARG means syntax error
|
|
//
|
|
// E_DAV_RANGE_NOT_SATISFIABLE if none of the ranges were valid
|
|
// for the entity size passed in.
|
|
//
|
|
// W_DAV_PARTIAL_CONTENT if there was at least one valid range.
|
|
//
|
|
// This function does NOT normally return S_OK. Only one of the above!
|
|
//
|
|
SCODE
|
|
CRangeParser::ScParseByteRangeHdr (LPCWSTR pwszRgHeader, DWORD dwSize)
|
|
{
|
|
SCODE sc = S_OK;
|
|
|
|
Assert(pwszRgHeader);
|
|
|
|
// Parses the ranges header and builds an array of the ranges
|
|
//
|
|
sc = ScParseRangeHdr (pwszRgHeader, gc_wszBytes);
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// Fixup the ranges as needed
|
|
//
|
|
sc = ScFixupRanges (dwSize);
|
|
Assert ((sc == W_DAV_PARTIAL_CONTENT) ||
|
|
(sc == E_DAV_RANGE_NOT_SATISFIABLE));
|
|
|
|
ret:
|
|
return sc;
|
|
}
|
|
|
|
// Class CRangeIter ----------------------------------------------------------
|
|
//
|
|
CRangeIter::~CRangeIter()
|
|
{
|
|
}
|
|
|
|
SCODE
|
|
CRangeIter::ScInit (ULONG cRGList, const RGITEM * prgRGList, ULONG cbSize)
|
|
{
|
|
SCODE sc = S_OK;
|
|
|
|
// The object must not have been initialized before
|
|
//
|
|
Assert (!m_pbData.get() && (0 == m_cRGList));
|
|
|
|
// Make sure we are given good bits...
|
|
//
|
|
Assert (cRGList);
|
|
Assert (prgRGList);
|
|
Assert (cbSize);
|
|
|
|
// Duplicate the RGITEM array
|
|
//
|
|
m_pbData = static_cast<BYTE*>(ExAlloc(cbSize));
|
|
if (!m_pbData.get())
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
CopyMemory (m_pbData.get(), prgRGList, cbSize);
|
|
|
|
// Remember the count and size
|
|
//
|
|
m_cRGList = cRGList;
|
|
m_cbSize = cbSize;
|
|
|
|
// Rewind to the beginning of the ranges
|
|
//
|
|
Rewind();
|
|
|
|
ret:
|
|
return sc;
|
|
}
|
|
|
|
// Range Parsing -------------------------------------------------------------
|
|
//
|
|
SCODE
|
|
ScParseOneWideRange (LPCWSTR pwsz, DWORD * pdwStart, DWORD * pdwEnd)
|
|
{
|
|
BOOL fEnd = FALSE;
|
|
BOOL fStart = FALSE;
|
|
DWORD dwEnd = static_cast<DWORD>(RANGE_NOT_PRESENT);
|
|
DWORD dwStart = static_cast<DWORD>(RANGE_NOT_PRESENT);
|
|
LPCWSTR pwszEnd;
|
|
SCODE sc = S_OK;
|
|
|
|
// A quick note about the format here...
|
|
//
|
|
// row_range= digit* '-' digit*
|
|
// digit= [0-9]
|
|
//
|
|
// So, the first thing we need to check is if there is a leading set of
|
|
// digits to indicate a starting point.
|
|
//
|
|
pwszEnd = _wcsspnp (pwsz, gc_wszDigits);
|
|
|
|
// If the return value is NULL, or points to a NULL, then we have an
|
|
// invalid range. It is not valid to simply have a set of digits
|
|
//
|
|
if ((NULL == pwszEnd) || (0 == *pwszEnd))
|
|
{
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
//
|
|
// Else if the current position and the end refer to the same
|
|
// character, then there is no starting range.
|
|
//
|
|
else if (pwsz != pwszEnd)
|
|
{
|
|
dwStart = wcstoul (pwsz, NULL, 10 /* always base 10 */);
|
|
pwsz = pwszEnd;
|
|
fStart = TRUE;
|
|
}
|
|
|
|
// Regardless, at this point we should have a single '-' character
|
|
//
|
|
if (L'-' != *pwsz++)
|
|
{
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
// Any remaining characters should be the end of the range
|
|
//
|
|
if (0 != *pwsz)
|
|
{
|
|
pwszEnd = _wcsspnp (pwsz, gc_wszDigits);
|
|
|
|
// Here we expect that the return value is not the same as
|
|
// the initial pointer
|
|
//
|
|
if ((NULL != pwszEnd) && (0 != pwszEnd))
|
|
{
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
dwEnd = wcstoul (pwsz, NULL, 10 /* always base 10 */);
|
|
fEnd = TRUE;
|
|
}
|
|
|
|
// Can't have both end-points as non-existant ranges
|
|
//
|
|
if ((!fStart && !fEnd) ||
|
|
(fStart && fEnd && (dwEnd < dwStart)))
|
|
{
|
|
sc = E_INVALIDARG;
|
|
goto ret;
|
|
}
|
|
|
|
ret:
|
|
*pdwStart = dwStart;
|
|
*pdwEnd = dwEnd;
|
|
return sc;
|
|
}
|
|
|
|
// ScGenerateContentRange() --------------------------------------------------
|
|
//
|
|
enum { BUFFER_INITIAL_SIZE = 512 };
|
|
|
|
// ScGenerateContentRange
|
|
//
|
|
// Helper function to build the content-range header
|
|
//
|
|
// If ulTotal is RGITER_TOTAL_UNKNOWN ((ULONG)-1), then we give back "total=*".
|
|
// This is needed for REPL, because our store api doesn't tell us how many possible
|
|
// rows there are up front.
|
|
//
|
|
SCODE ScGenerateContentRange (
|
|
/* [in] */ LPCSTR pszRangeUnit,
|
|
/* [in] */ const RGITEM * prgRGList,
|
|
/* [in] */ ULONG cRanges,
|
|
/* [in] */ ULONG cbRanges,
|
|
/* [in] */ ULONG ulTotal,
|
|
/* [out] */ LPSTR *ppszContentRange)
|
|
{
|
|
auto_heap_ptr<CHAR> pszCR;
|
|
BOOL fMultipleRanges = FALSE;
|
|
CRangeIter cri;
|
|
SCODE sc = E_INVALIDARG;
|
|
ULONG cb = 0;
|
|
ULONG cbSize = BUFFER_INITIAL_SIZE;
|
|
|
|
// We must have something to emit
|
|
//
|
|
Assert (ppszContentRange);
|
|
Assert (cRanges);
|
|
|
|
sc = cri.ScInit (cRanges, prgRGList, cbRanges);
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// Allocate the space for the header
|
|
//
|
|
pszCR = static_cast<LPSTR>(ExAlloc (cbSize));
|
|
if (!pszCR.get())
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
|
|
// Setup the leading range units, etc...
|
|
//
|
|
strcpy (pszCR.get() + cb, pszRangeUnit);
|
|
cb += static_cast<ULONG>(strlen(pszRangeUnit));
|
|
|
|
// Stuff in a leading space
|
|
//
|
|
pszCR.get()[cb++] = ' ';
|
|
|
|
// Now iterate through the ranges to add in
|
|
// each range.
|
|
//
|
|
while (NULL != (prgRGList = cri.PrgiNextRange()))
|
|
{
|
|
// If the range is unknown, then it is a range
|
|
// that was not processed on the store side.
|
|
//
|
|
if (RANGE_UNKNOWN == prgRGList->uRT)
|
|
continue;
|
|
|
|
// First off, make sure there is plenty of room
|
|
//
|
|
if (cb > cbSize - 50)
|
|
{
|
|
// Realloc the buffer
|
|
//
|
|
cbSize = cbSize + BUFFER_INITIAL_SIZE;
|
|
pszCR.realloc (cbSize);
|
|
|
|
// It's possible that the allocation fails
|
|
//
|
|
if (!pszCR.get())
|
|
goto ret;
|
|
}
|
|
|
|
// Now that we know we have space...
|
|
// If this is a subsequent range to the initial
|
|
// one, add in a comma.
|
|
//
|
|
if (fMultipleRanges)
|
|
{
|
|
// Stuff in a comma and a space
|
|
//
|
|
pszCR.get()[cb++] = ',';
|
|
pszCR.get()[cb++] = ' ';
|
|
}
|
|
|
|
if (RANGE_ROW == prgRGList->uRT)
|
|
{
|
|
// 50 is a safe numder of bytes to hold the last range and
|
|
// "total = <size>"
|
|
//
|
|
// Append the next range
|
|
//
|
|
cb += sprintf (pszCR.get() + cb,
|
|
"%u-%u",
|
|
prgRGList->dwrgi.dwFirst,
|
|
prgRGList->dwrgi.dwLast);
|
|
}
|
|
else
|
|
{
|
|
// For all non-row ranges, we really don't know the ordinals
|
|
// of the rows up front. We only find that info out when the
|
|
// rows are actually queried. This happens long after the
|
|
// content-range header is constructed, so we stuff in a place
|
|
// holder for these ranges.
|
|
//
|
|
pszCR.get()[cb++] = '*';
|
|
}
|
|
fMultipleRanges = TRUE;
|
|
}
|
|
|
|
// Now it's time to append the "total=<size>"
|
|
// Handle the special case of RGITER_TOTAL_UNKNOWN -- give "total=*".
|
|
//
|
|
if (RANGE_TOTAL_UNKNOWN == ulTotal)
|
|
{
|
|
const char rgTotalStar[] = "; total=*";
|
|
memcpy (pszCR.get() + cb, rgTotalStar, CElems(rgTotalStar));
|
|
}
|
|
else
|
|
{
|
|
sprintf(pszCR.get() + cb, "; total=%u", ulTotal);
|
|
}
|
|
|
|
// Pass the buffer back
|
|
//
|
|
*ppszContentRange = pszCR.relinquish();
|
|
sc = S_OK;
|
|
|
|
ret:
|
|
return sc;
|
|
}
|