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