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.
 
 
 
 
 
 

676 lines
19 KiB

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name :
range.cxx
Abstract:
Handle Range Requests
Author:
Anil Ruia (AnilR) 29-Mar-2000
Environment:
Win32 - User Mode
Project:
UlW3.dll
--*/
#include "precomp.hxx"
#include "staticfile.hxx"
#define RANGE_BOUNDARY "<q1w2e3r4t5y6u7i8o9p0zaxscdvfbgnhmjklkl>"
#define MAX_RANGE_ALLOWED 5
extern BOOL FindInETagList(LPCSTR pLocalETag,
LPCSTR pETagList,
BOOL fWeakCompare);
STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeContentType;
STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeMidDelimiter;
STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeEndDelimiter;
DWORD W3_STATIC_FILE_HANDLER::sm_dwMaxRangeAllowed = MAX_RANGE_ALLOWED;
// static
HRESULT W3_STATIC_FILE_HANDLER::Initialize()
{
ALLOC_CACHE_CONFIGURATION acConfig;
HRESULT hr = NO_ERROR;
//
// Setup allocation lookaside
//
acConfig.nConcurrency = 1;
acConfig.nThreshold = 100;
acConfig.cbSize = sizeof( W3_STATIC_FILE_HANDLER );
DBG_ASSERT( sm_pachStaticFileHandlers == NULL );
sm_pachStaticFileHandlers = new ALLOC_CACHE_HANDLER( "W3_STATIC_FILE_HANDLER",
&acConfig );
if ( sm_pachStaticFileHandlers == NULL )
{
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
goto Failed;
}
//
// Initialize the various Range Strings
//
sm_pstrRangeContentType = new STRA;
sm_pstrRangeMidDelimiter = new STRA;
sm_pstrRangeEndDelimiter = new STRA;
if (sm_pstrRangeContentType == NULL ||
sm_pstrRangeMidDelimiter == NULL ||
sm_pstrRangeEndDelimiter == NULL)
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto Failed;
}
if (FAILED(hr = sm_pstrRangeContentType->Copy(
"multipart/byteranges; boundary=")) ||
FAILED(hr = sm_pstrRangeContentType->Append(RANGE_BOUNDARY)))
{
goto Failed;
}
if (FAILED(hr = sm_pstrRangeMidDelimiter->Copy("--")) ||
FAILED(hr = sm_pstrRangeMidDelimiter->Append(RANGE_BOUNDARY)) ||
FAILED(hr = sm_pstrRangeMidDelimiter->Append("\r\n")))
{
goto Failed;
}
if (FAILED(hr = sm_pstrRangeEndDelimiter->Copy("\r\n--")) ||
FAILED(hr = sm_pstrRangeEndDelimiter->Append(RANGE_BOUNDARY)) ||
FAILED(hr = sm_pstrRangeEndDelimiter->Append("--\r\n\r\n")))
{
goto Failed;
}
HKEY w3Params;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
W3_PARAMETERS_KEY,
0,
KEY_READ,
&w3Params) == NO_ERROR)
{
DWORD dwType;
DWORD cbData = sizeof DWORD;
if ((RegQueryValueEx(w3Params,
L"MaximumRangeAllowed",
NULL,
&dwType,
(LPBYTE)&sm_dwMaxRangeAllowed,
&cbData) != NO_ERROR) ||
(dwType != REG_DWORD))
{
sm_dwMaxRangeAllowed = MAX_RANGE_ALLOWED;
}
RegCloseKey(w3Params);
}
return S_OK;
Failed:
Terminate();
return hr;
}
BOOL ScanRange(LPCSTR *ppszRange,
ULARGE_INTEGER liFileSize,
ULARGE_INTEGER *pliOffset,
ULARGE_INTEGER *pliSize,
BOOL *pfInvalidRange)
/*++
Routine Description:
Scan the next range in strRange
Returns:
TRUE if a range was found, else FALSE
Arguments:
ppszRange String pointing to the current range.
liFileSize Size of the file being retrieved
pliOffset range offset on return
pliSizeTo range size on return
pfInvalidRange set to TRUE on return if Invalid syntax
--*/
{
LPCSTR pszRange = *ppszRange;
//
// Skip to begining of next range
//
while (*pszRange == ' ')
{
++pszRange;
}
//
// Test for no range
//
if (*pszRange == '\0')
{
return FALSE;
}
//
// determine Offset & Size to send
//
if (*pszRange == '-')
{
//
// This is in format -nnn which means send bytes filesize-nnn
// to eof
//
++pszRange;
if (!isdigit(*pszRange))
{
*pfInvalidRange = TRUE;
return TRUE;
}
pliSize->QuadPart = _atoi64(pszRange);
if (pliSize->QuadPart > liFileSize.QuadPart)
{
pliSize->QuadPart = liFileSize.QuadPart;
pliOffset->QuadPart = 0;
}
else
{
pliOffset->QuadPart = liFileSize.QuadPart - pliSize->QuadPart;
}
}
else if (isdigit(*pszRange))
{
//
// This is in format mmm-nnn which menas send bytes mmm to nnn
// or format mmm- which means send bytes mmm to eof
//
pliOffset->QuadPart = _atoi64(pszRange);
//
// Skip over the beginning number - and any intervening spaces
//
while(isdigit(*pszRange))
{
pszRange++;
}
while(*pszRange == ' ')
{
pszRange++;
}
if (*pszRange != '-')
{
*pfInvalidRange = TRUE;
return TRUE;
}
pszRange++;
while(*pszRange == ' ')
{
pszRange++;
}
if (isdigit(*pszRange))
{
//
// We have mmm-nnn
//
ULARGE_INTEGER liEnd;
liEnd.QuadPart = _atoi64(pszRange);
if (liEnd.QuadPart < pliOffset->QuadPart)
{
*pfInvalidRange = TRUE;
return TRUE;
}
if (liEnd.QuadPart >= liFileSize.QuadPart)
{
pliSize->QuadPart = liFileSize.QuadPart - pliOffset->QuadPart;
}
else
{
pliSize->QuadPart = liEnd.QuadPart - pliOffset->QuadPart + 1;
}
}
else
{
//
// We have mmm-
//
pliSize->QuadPart = liFileSize.QuadPart - pliOffset->QuadPart;
}
}
else
{
//
// Invalid Syntax
//
*pfInvalidRange = TRUE;
return TRUE;
}
//
// Skip to the start of the next range
//
while (isdigit(*pszRange))
{
++pszRange;
}
while (*pszRange == ' ')
{
++pszRange;
}
if (*pszRange == ',')
{
++pszRange;
}
else if (*pszRange != '\0')
{
*pfInvalidRange = TRUE;
return TRUE;
}
*ppszRange = pszRange;
return TRUE;
}
HRESULT W3_STATIC_FILE_HANDLER::RangeDoWork(W3_CONTEXT *pW3Context,
W3_FILE_INFO *pOpenFile,
BOOL *pfHandled)
{
W3_REQUEST *pRequest = pW3Context->QueryRequest();
W3_RESPONSE *pResponse = pW3Context->QueryResponse();
// First, handle If-Range: if it exists. If the If-Range matches we
// don't do anything. If the If-Range doesn't match then we force
// retrieval of the whole file.
LPCSTR pszIfRange = pRequest->GetHeader(HttpHeaderIfRange);
if (pszIfRange != NULL && *pszIfRange != L'\0')
{
// Need to determine if what we have is a date or an ETag.
// An ETag may start with a W/ or a quote. A date may start
// with a W but will never have the second character be a /.
if (*pszIfRange == L'"' ||
(*pszIfRange == L'W' && pszIfRange[1] == L'/'))
{
// This is an ETag.
if (pOpenFile->QueryIsWeakETag() ||
!FindInETagList(pOpenFile->QueryETag(),
pszIfRange,
FALSE))
{
// The If-Range failed, so we can't send a range. Force
// sending the whole thing.
*pfHandled = FALSE;
return S_OK;
}
}
else
{
// This is a date
LARGE_INTEGER liRangeTime;
// This must be a date. Convert it to a time, and see if it's
// less than or equal to our last write time. If it is, the
// file's changed, and we can't perform the range.
if (!StringTimeToFileTime(pszIfRange, &liRangeTime))
{
// Couldn't convert it, so don't send the range.
*pfHandled = FALSE;
return S_OK;
}
else
{
FILETIME tm;
pOpenFile->QueryLastWriteTime(&tm);
if (*(LONGLONG*)&tm > liRangeTime.QuadPart )
{
// The If-Range failed, so we can't send a range. Force
// sending the whole thing.
*pfHandled = FALSE;
return S_OK;
}
}
}
}
// If we fell through, then If-Range passed, we are going to try sending
// a range response
LPCSTR pszRange = pRequest->GetHeader(HttpHeaderRange);
//
// We have bytes = <range1>, <range2>, ...
// skip past the '='
//
pszRange = strchr(pszRange, '=');
if (pszRange == NULL)
{
// Invalid syntax, send entire file
*pfHandled = FALSE;
return S_OK;
}
pszRange++;
ULARGE_INTEGER liFileSize;
HRESULT hr = S_OK;
pOpenFile->QuerySize(&liFileSize);
if (liFileSize.QuadPart <= 0)
{
pResponse->ClearHeaders();
pResponse->SetStatus(HttpStatusRangeNotSatisfiable);
CHAR pszContentRange[32];
strcpy(pszContentRange, "bytes */");
_i64toa(liFileSize.QuadPart, pszContentRange + 8, 10);
if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange,
pszContentRange,
(USHORT)strlen(pszContentRange))))
{
return hr;
}
*pfHandled = TRUE;
return S_OK;
}
DWORD cRanges = 0;
STACK_BUFFER( bufByteRange, 256);
HTTP_BYTE_RANGE *pByteRange;
ULARGE_INTEGER liRangeOffset;
ULARGE_INTEGER liRangeSize;
BOOL fInvalidSyntax = FALSE;
BOOL fAtLeastOneRange = FALSE;
ULONGLONG dwTotalRangeBytes = 0;
while (ScanRange(&pszRange,
liFileSize,
&liRangeOffset,
&liRangeSize,
&fInvalidSyntax))
{
fAtLeastOneRange = TRUE;
if (fInvalidSyntax)
{
//
// Invalid syntax in Range header. Ignore Range headers,
// Send complete File.
//
*pfHandled = FALSE;
return S_OK;
}
if (liRangeOffset.QuadPart > liFileSize.QuadPart ||
liRangeSize.QuadPart == 0)
{
//
// The Range is not satisfiable
//
continue;
}
cRanges++;
if (cRanges * sizeof(HTTP_BYTE_RANGE) > bufByteRange.QuerySize())
{
if (!bufByteRange.Resize(cRanges * sizeof(HTTP_BYTE_RANGE), 256))
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
}
pByteRange = (HTTP_BYTE_RANGE *)bufByteRange.QueryPtr();
pByteRange[cRanges - 1].StartingOffset.QuadPart = liRangeOffset.QuadPart;
pByteRange[cRanges - 1].Length.QuadPart = liRangeSize.QuadPart;
//
// Keep track of the total bytes in all the ranges combined
//
dwTotalRangeBytes += liRangeSize.QuadPart;
}
if (dwTotalRangeBytes > (liFileSize.QuadPart * sm_dwMaxRangeAllowed))
{
pResponse->ClearHeaders();
pResponse->SetStatus(HttpStatusBadRequest);
*pfHandled = TRUE;
return S_OK;
}
if (!fAtLeastOneRange)
{
//
// Syntactically invalid, ignore the range header
//
*pfHandled = FALSE;
return S_OK;
}
if (cRanges == 0)
{
//
// No byte ranges are satisfiable
//
pResponse->ClearHeaders();
pResponse->SetStatus(HttpStatusRangeNotSatisfiable);
CHAR pszContentRange[32];
strcpy(pszContentRange, "bytes */");
_i64toa(liFileSize.QuadPart, pszContentRange + 8, 10);
if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange,
pszContentRange,
(USHORT)strlen(pszContentRange))))
{
return hr;
}
*pfHandled = TRUE;
return S_OK;
}
*pfHandled = TRUE;
pResponse->SetStatus(HttpStatusPartialContent);
STRA *pstrContentType = pW3Context->QueryUrlContext()->QueryUrlInfo()->
QueryContentType();
STACK_STRA ( strPartContentType, 128);
if (cRanges == 1)
{
//
// Only one chunk, Content-Type is same as that of complete entity
//
if (FAILED(hr = pResponse->SetHeaderByReference(
HttpHeaderContentType,
pstrContentType->QueryStr(),
(USHORT)pstrContentType->QueryCCH())))
{
return hr;
}
}
else
{
//
// Content-Type is multipart/byteranges; boundary=....
//
if (FAILED(hr = pResponse->SetHeaderByReference(
HttpHeaderContentType,
sm_pstrRangeContentType->QueryStr(),
(USHORT)sm_pstrRangeContentType->QueryCCH())))
{
return hr;
}
//
// The actual content-type of the entity to be sent with each part
//
if (FAILED(hr = strPartContentType.Copy("Content-Type: ")) ||
FAILED(hr = strPartContentType.Append(*pstrContentType)) ||
FAILED(hr = strPartContentType.Append("\r\n")))
{
return hr;
}
}
//
// build the response
//
STRA strContentRange;
STRA strDelimiter;
pByteRange = (HTTP_BYTE_RANGE *)bufByteRange.QueryPtr();
for (DWORD i = 0; i < cRanges; i++)
{
liRangeOffset.QuadPart = pByteRange[i].StartingOffset.QuadPart;
liRangeSize.QuadPart = pByteRange[i].Length.QuadPart;
//
// Build up the Content-Range header
//
CHAR pszSize[32];
if (FAILED(hr = strContentRange.Copy("bytes ")))
{
return hr;
}
_i64toa(liRangeOffset.QuadPart, pszSize, 10);
if (FAILED(hr = strContentRange.Append(pszSize)) ||
FAILED(hr = strContentRange.Append("-")))
{
return hr;
}
_i64toa(liRangeOffset.QuadPart + liRangeSize.QuadPart - 1,
pszSize, 10);
if (FAILED(hr = strContentRange.Append(pszSize)) ||
FAILED(hr = strContentRange.Append("/")))
{
return hr;
}
_i64toa(liFileSize.QuadPart, pszSize, 10);
if (FAILED(hr = strContentRange.Append(pszSize)))
{
return hr;
}
if (cRanges == 1)
{
//
// If only one chunk, send Content-Range as a header
//
if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange,
strContentRange.QueryStr(),
(USHORT)strContentRange.QueryCCH())))
{
return hr;
}
}
else
{
if (i > 0)
{
// Already sent a range, add a newline
if (FAILED(hr = strDelimiter.Copy("\r\n", 2)))
{
return hr;
}
}
//
// Add delimiter, Content-Type, Content-Range
//
if (FAILED(hr = strDelimiter.Append(*sm_pstrRangeMidDelimiter)) ||
FAILED(hr = strDelimiter.Append(strPartContentType)) ||
FAILED(hr = strDelimiter.Append(HEADER("Content-Range: "))) ||
FAILED(hr = strDelimiter.Append(strContentRange)) ||
FAILED(hr = strDelimiter.Append("\r\n\r\n", 4)))
{
return hr;
}
//
// store the ANSI version in the BUFFER chain
//
DWORD bufSize = strDelimiter.QueryCCH() + 1;
BUFFER_CHAIN_ITEM *pBCI = new BUFFER_CHAIN_ITEM(bufSize);
if (pBCI == NULL)
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
memcpy(pBCI->QueryPtr(),
strDelimiter.QueryStr(),
bufSize);
if (!m_RangeBufferChain.AppendBuffer(pBCI))
{
return HRESULT_FROM_WIN32(GetLastError());
}
//
// Now actually add this delimiter chunk
//
if (FAILED(hr = pResponse->AddMemoryChunkByReference(
pBCI->QueryPtr(),
bufSize - 1)))
{
return hr;
}
}
//
// Add the actual file chunk
//
if (pOpenFile->QueryFileBuffer() != NULL &&
liRangeSize.HighPart == 0 &&
liRangeOffset.HighPart == 0)
{
hr = pResponse->AddMemoryChunkByReference(
pOpenFile->QueryFileBuffer() + liRangeOffset.LowPart,
liRangeSize.LowPart);
}
else
{
hr = pResponse->AddFileHandleChunk( pOpenFile->QueryFileHandle(),
liRangeOffset.QuadPart,
liRangeSize.QuadPart );
}
if (FAILED(hr))
{
return hr;
}
}
if (cRanges > 1)
{
//
// Add the terminating delimiter
//
if (FAILED(hr = pResponse->AddMemoryChunkByReference(
sm_pstrRangeEndDelimiter->QueryStr(),
sm_pstrRangeEndDelimiter->QueryCCH())))
{
return hr;
}
}
return S_OK;
}