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

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// 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;
}