/*++ 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 "" extern BOOL FindInETagList(CHAR * pLocalETag, CHAR * pETagList, BOOL fWeakCompare); STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeContentType; STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeMidDelimiter; STRA *W3_STATIC_FILE_HANDLER::sm_pstrRangeEndDelimiter; // 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 ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } // // 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) { delete sm_pachStaticFileHandlers; sm_pachStaticFileHandlers = NULL; return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } if (FAILED(hr = sm_pstrRangeContentType->Copy( "multipart/byteranges; boundary=")) || FAILED(hr = sm_pstrRangeContentType->Append(RANGE_BOUNDARY))) { delete sm_pachStaticFileHandlers; sm_pachStaticFileHandlers = NULL; return hr; } if (FAILED(hr = sm_pstrRangeMidDelimiter->Copy("--")) || FAILED(hr = sm_pstrRangeMidDelimiter->Append(RANGE_BOUNDARY)) || FAILED(hr = sm_pstrRangeMidDelimiter->Append("\r\n"))) { delete sm_pachStaticFileHandlers; sm_pachStaticFileHandlers = NULL; return hr; } if (FAILED(hr = sm_pstrRangeEndDelimiter->Copy("\r\n--")) || FAILED(hr = sm_pstrRangeEndDelimiter->Append(RANGE_BOUNDARY)) || FAILED(hr = sm_pstrRangeEndDelimiter->Append("--\r\n\r\n"))) { delete sm_pachStaticFileHandlers; sm_pachStaticFileHandlers = NULL; return hr; } return S_OK; } BOOL ScanRange(CHAR * *ppszRange, LARGE_INTEGER liFileSize, LARGE_INTEGER *pliOffset, LARGE_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 --*/ { CHAR *pszRange = *ppszRange; CHAR c; // // Skip to begining of next range // while ((c = *pszRange) && (c == ' ')) { ++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((c = *pszRange) && isdigit(c)) { pszRange++; } while((c = *pszRange) && (c == ' ')) { pszRange++; } if (*pszRange != '-') { *pfInvalidRange = TRUE; return TRUE; } pszRange++; while((c = *pszRange) && (c == ' ')) { pszRange++; } if (isdigit(*pszRange)) { // // We have mmm-nnn // LARGE_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 ((c = *pszRange) && isdigit(c)) { ++pszRange; } while ((c = *pszRange) && c == ' ') { ++pszRange; } if (c == ',') { ++pszRange; } else if (c != '\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. CHAR * 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 CHAR * pszRange = pRequest->GetHeader(HttpHeaderRange); // // We have bytes = , , ... // skip past the '=' // pszRange = strchr(pszRange, '='); if (pszRange == NULL) { // Invalid syntax, send entire file *pfHandled = FALSE; return S_OK; } pszRange++; LARGE_INTEGER liFileSize; HRESULT hr = S_OK; pOpenFile->QuerySize(&liFileSize); if (liFileSize.QuadPart <= 0) { pResponse->ClearHeaders(); pResponse->SetStatus(HttpStatusRangeNotSatisfiable); CHAR pszContentRange[24]; strcpy(pszContentRange, "bytes */"); _i64toa(liFileSize.QuadPart, pszContentRange + 8, 10); if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange, pszContentRange, strlen(pszContentRange)))) { return hr; } *pfHandled = TRUE; return S_OK; } DWORD cRanges = 0; STACK_BUFFER( bufByteRange, 256); HTTP_BYTE_RANGE *pByteRange; LARGE_INTEGER liRangeOffset; LARGE_INTEGER liRangeSize; BOOL fInvalidSyntax = FALSE; BOOL fAtLeastOneRange = FALSE; 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 = liRangeOffset; pByteRange[cRanges - 1].Length = liRangeSize; } 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[24]; strcpy(pszContentRange, "bytes */"); _i64toa(liFileSize.QuadPart, pszContentRange + 8, 10); if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentRange, pszContentRange, 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(), pstrContentType->QueryCCH()))) { return hr; } } else { // // Content-Type is multipart/byteranges; boundary=.... // if (FAILED(hr = pResponse->SetHeaderByReference( HttpHeaderContentType, sm_pstrRangeContentType->QueryStr(), 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 = pByteRange[i].StartingOffset; liRangeSize = pByteRange[i].Length; // // Build up the Content-Range header // CHAR pszSize[24]; 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(), 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("Content-Range: ", 15)) || 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; }