// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // 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 #pragma warning(disable:4201) // nameless struct/union #pragma warning(disable: 4284) // operator-> to a non UDT #include #include #include #include #include #include #include #include // 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(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(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(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(m_prgi); } // Scoot forward // m_prgi = reinterpret_cast(pb + cb); m_iCur += 1; // Ensure the boundry // Assert (reinterpret_cast(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(ExAlloc(m_cbSize)); m_prgi = reinterpret_cast(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(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(RANGE_NOT_PRESENT); DWORD dwStart = static_cast(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 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(ExAlloc (cbSize)); if (!pszCR.get()) { sc = E_OUTOFMEMORY; goto ret; } // Setup the leading range units, etc... // strcpy (pszCR.get() + cb, pszRangeUnit); cb += static_cast(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 = " // // 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=" // 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; }