CALLBACK.CPP
// =========================================================================== 
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 
// PARTICULAR PURPOSE. 
// 
// Copyright 1996 Microsoft Corporation.  All Rights Reserved. 
// =========================================================================== 
#include <urlmon.h> 
#include <wininet.h> 
#include "callback.hpp" 
 
#define BOUNDARY_MAXSIZE 500  
#define RECV_BUF_SIZE   8192 
 
static char szCRLF[] = "\r\n"; 
static char szLF[]   = "\n"; 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::CUrlmonCallback 
// --------------------------------------------------------------------------- 
CUrlmonCallback::CUrlmonCallback (PHTTP_REQUEST_PARAM pParam) 
{ 
    m_pBinding = NULL; 
    m_pstm = NULL; 
    m_cRef = 0; 
 
    m_pParam = pParam; 
 
    if (m_pParam->punkOuter) 
        ((IUnknown *) m_pParam->punkOuter)->AddRef(); 
     
    m_CBParam.cbStruct = sizeof(m_CBParam); 
    m_CBParam.dwRequestCtx = m_pParam->dwRequestCtx; 
 
    m_dwOffset = 0; 
    m_dwResponseCode = 0; 
    m_pszRangeDelimiter = NULL; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::~CUrlmonCallback 
// --------------------------------------------------------------------------- 
CUrlmonCallback::~CUrlmonCallback() 
{ 
    if (m_pParam->punkOuter) 
        ((IUnknown *) m_pParam->punkOuter)->Release(); 
    if (m_pstm) 
        m_pstm->Release(); 
    if (m_pszRangeDelimiter) 
        LocalFree ((HLOCAL) m_pszRangeDelimiter); 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::QueryInterface 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::QueryInterface(REFIID riid, void** ppv) 
{  
    if(IsEqualGUID(riid,IID_IUnknown)) 
        *ppv = (IUnknown *) (IBindStatusCallback *) this; 
    else if (IsEqualGUID(riid,IID_IBindStatusCallback)) 
        *ppv = (IBindStatusCallback *) this; 
    else if (IsEqualGUID(riid, IID_IHttpNegotiate)) 
        *ppv = (IHttpNegotiate *) this; 
    else 
        *ppv = NULL; 
 
    if (!*ppv) 
        return E_NOINTERFACE; 
     
    // Increment our reference count before we hand out our interface 
    ((LPUNKNOWN)*ppv)->AddRef(); 
    return S_OK; 
 
} 
 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::AddRef 
// --------------------------------------------------------------------------- 
STDMETHODIMP_(ULONG) CUrlmonCallback::AddRef(void) 
{ 
    if (m_pParam->punkOuter) 
        ((IUnknown *) m_pParam->punkOuter)->AddRef(); 
    return m_cRef++; 
} 
 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::Release 
// --------------------------------------------------------------------------- 
STDMETHODIMP_(ULONG) CUrlmonCallback::Release(void) 
{ 
    if (m_pParam->punkOuter) 
        ((IUnknown *) m_pParam->punkOuter)->Release(); 
 
    if (--m_cRef == 0) 
    { 
        delete this; 
        return 0; 
    } 
    return m_cRef; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::BeginningTransaction 
// --------------------------------------------------------------------------- 
STDMETHODIMP 
CUrlmonCallback::BeginningTransaction (LPCWSTR szURL, 
    LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *ppszAdditionalHeaders) 
{ 
    static char szRangeHeader[] = "Range: bytes="; 
    static char szUnlessHeader[] = "Unless-Modified-Since: "; 
 
    HRESULT hr = S_OK; 
    PSTR pszHeader = NULL; 
     
    // Don't add any headers if not a range request. 
    DWORD cRanges = m_pParam->cRanges; 
    if (!cRanges) 
    { 
        *ppszAdditionalHeaders = NULL; 
        goto done; 
    } 
 
    PHTTP_REQUEST_RANGE pRanges; 
    pRanges = m_pParam->pRanges; 
 
    // Allocate ANSI buffer. 
    DWORD cbHeader; 
    cbHeader = sizeof(szRangeHeader) + 20 * cRanges + 2; 
    if (m_pParam->pstUnlessModifiedSince) 
       cbHeader += sizeof(szUnlessHeader) + INTERNET_RFC1123_BUFSIZE + 2; 
    pszHeader = (PSTR) LocalAlloc (LMEM_FIXED, cbHeader); 
    if (!pszHeader) 
    { 
        hr = E_OUTOFMEMORY; 
        goto done; 
    } 
 
    // Format the read range request header. 
    UINT cchHeader; 
    cchHeader = wsprintf (pszHeader, "%s", szRangeHeader); 
 
    // Add the ranges. 
    while (cRanges--) 
    { 
        if (pRanges->dwSize) 
        { 
            // Format range, end value is inclusive. 
            cchHeader += wsprintf (pszHeader + cchHeader, "%d-%d", 
                pRanges->dwOffset, pRanges->dwOffset + pRanges->dwSize - 1); 
        } 
        else 
        { 
            // Format range to end of file. 
            cchHeader += wsprintf (pszHeader + cchHeader, "%d-", 
                pRanges->dwOffset); 
        } 
             
        pRanges++; 
        if (cRanges) 
           cchHeader += wsprintf (pszHeader + cchHeader, ", "); 
        else 
           cchHeader += wsprintf (pszHeader + cchHeader, szCRLF); 
    } 
 
    if (m_pParam->pstUnlessModifiedSince) 
    { 
        // Add unless-modified-since qualifier. 
        cchHeader += wsprintf (pszHeader + cchHeader, szUnlessHeader); 
        if (!InternetTimeFromSystemTime 
        ( 
            m_pParam->pstUnlessModifiedSince, 
            INTERNET_RFC1123_FORMAT, 
            pszHeader + cchHeader, 
            INTERNET_RFC1123_BUFSIZE 
        )) 
        { 
            hr = E_FAIL; 
            goto done; 
        } 
         
        cchHeader += lstrlen (pszHeader + cchHeader); 
        cchHeader += wsprintf (pszHeader + cchHeader, szCRLF); 
    } 
 
    cchHeader++; // for NULL termination 
 
    // Allocate Unicode buffer. 
    *ppszAdditionalHeaders = (WCHAR *) CoTaskMemAlloc (sizeof(WCHAR) * cchHeader); 
    if (*ppszAdditionalHeaders) 
    { 
        MultiByteToWideChar (CP_ACP, 0, pszHeader, -1, *ppszAdditionalHeaders, 
            sizeof(WCHAR) * cchHeader); 
    } 
 
    hr = *ppszAdditionalHeaders? S_OK : E_OUTOFMEMORY; 
 
done: 
    if (pszHeader) 
        LocalFree (pszHeader); 
    return hr; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::OnResponse 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::OnResponse 
( 
    DWORD   dwResponseCode,  
    LPCWSTR szResponseHeaders, 
    LPCWSTR szRequestHeaders, 
    LPWSTR  *pszAdditionalRequestHeaders 
) 
{ 
    // Get the HttpQueryInfo wrapper object. 
    IWinInetHttpInfo *pHttpInfo = NULL; 
    HRESULT hr = m_pBinding->QueryInterface 
        (IID_IWinInetHttpInfo, (void **) &pHttpInfo); 
    if (FAILED(hr)) 
        goto done; 
 
    // Save the response code. 
    m_dwResponseCode = dwResponseCode; 
    m_CBParam.dwResponseCode = dwResponseCode; 
 
    DWORD cbBuf; 
 
    // Check for partial response. 
    if (dwResponseCode == 206) 
    { 
       // Server responded with byte ranges, ergo must support them. 
       m_CBParam.fdwRequestFlags = HTTP_REQUEST_ACCEPT_RANGES; 
 
       // Look for multi-part delimiter embedded in content type. 
       const static char szMultiPart[] = "multipart/x-byteranges; boundary"; 
       const static DWORD cbMultiPart = sizeof(szMultiPart); 
 
       // Get length of buffer to hold content type. 
       DWORD cbContentType = 0; 
       pHttpInfo->QueryInfo 
          (HTTP_QUERY_CONTENT_TYPE, NULL, &cbContentType, NULL, 0); 
 
       if (cbContentType > cbMultiPart + 1) 
       { 
            // Content type is big enough to embed a delimiter. 
            m_pszRangeDelimiter = (PSTR) LocalAlloc (LMEM_FIXED, cbContentType); 
            if (!m_pszRangeDelimiter) 
            { 
                hr = E_OUTOFMEMORY; 
                goto done; 
            } 
 
            if (S_OK != pHttpInfo->QueryInfo (HTTP_QUERY_CONTENT_TYPE, 
                 m_pszRangeDelimiter, &cbContentType, NULL, 0)) 
            { 
                hr = E_FAIL; 
                goto done; 
            }  
             
            // Split the string at the '=' 
            m_pszRangeDelimiter[cbMultiPart - 1] = 0; 
            if (lstrcmpi (m_pszRangeDelimiter, szMultiPart)) 
            { 
                // Response must not be multi-part. 
                LocalFree ((HLOCAL) m_pszRangeDelimiter); 
                m_pszRangeDelimiter = NULL; 
            } 
            else 
            { 
                m_cchRangeDelimiter = 
                    lstrlen (m_pszRangeDelimiter + cbMultiPart); 
 
                // Shift the delimiter to offset 2 of string. 
                MoveMemory 
                ( 
                  m_pszRangeDelimiter + 2, // +2 for prefix 
                  m_pszRangeDelimiter + cbMultiPart, 
                  m_cchRangeDelimiter + 1  // +1 for null 
                );     
 
                // Prefix delimiter with "--" 
                m_pszRangeDelimiter[0] = '-'; 
                m_pszRangeDelimiter[1] = '-'; 
                m_cchRangeDelimiter += 2; 
 
                // Initialize range boundaries. 
                m_dwRangeBeg = 0; 
                m_dwRangeEnd = 0; 
            } 
             
        } // end if (cbContentType > cbMultiPart + 1) 
 
        // Adjust the offset if we have a single range 
        if (!m_pszRangeDelimiter) 
            m_dwOffset = m_pParam->pRanges[0].dwOffset; 
    } 
     
    else // if (dwResponseCode != 206) 
    { 
        // Check if ranges are supported. 
        static char szBytes[] = "bytes"; 
        char szBuf[sizeof(szBytes)]; 
        cbBuf = sizeof(szBytes); 
        hr = pHttpInfo->QueryInfo 
            (HTTP_QUERY_ACCEPT_RANGES, szBuf, &cbBuf, NULL, 0); 
        if (SUCCEEDED(hr) && !lstrcmpi (szBytes, szBuf)) 
            m_CBParam.fdwRequestFlags = HTTP_REQUEST_ACCEPT_RANGES; 
        else 
            m_CBParam.fdwRequestFlags = 0; 
    } 
 
    // Get last modified time. 
    SYSTEMTIME stLastModified; 
    cbBuf = sizeof(stLastModified); 
    hr = pHttpInfo->QueryInfo 
    ( 
        HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED, 
        &stLastModified, &cbBuf, NULL, 0 
    ); 
    if (hr != S_OK) 
    { 
        memset (&stLastModified, 0, sizeof(stLastModified)); 
        hr = S_OK; 
    } 
    m_CBParam.pstLastModified = &stLastModified; 
                 
    // Get content length. 
    m_CBParam.dwContentLength = 0; 
    if (dwResponseCode != 206) 
    {    
        cbBuf = sizeof(m_CBParam.dwContentLength); 
        hr = pHttpInfo->QueryInfo 
        ( 
            HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, 
            &m_CBParam.dwContentLength, &cbBuf, NULL, 0 
        ); 
        if (hr != S_OK) 
        { 
            m_CBParam.dwContentLength = 0; 
            hr = S_OK; 
        } 
    } 
 
 
    // Inform the client the request is started. 
    m_CBParam.CallbackType = REQUESTCB_STARTED; 
    if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam)) 
         m_pBinding->Abort(); 
    hr = S_OK; 
 
done: 
 
    if (pHttpInfo) 
        pHttpInfo->Release(); 
     
    return hr; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::OnStartBinding 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::OnStartBinding 
    (DWORD grfBSCOption, IBinding* pBinding) 
{ 
    if (pBinding != NULL) 
    { 
         m_pBinding = pBinding; 
         m_pBinding->AddRef(); 
    } 
     
    // Initialize receive buffer. 
    if (!BufAlloc(RECV_BUF_SIZE)) 
        return E_OUTOFMEMORY; 
         
    return S_OK; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::GetPriority 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::GetPriority(LONG* pnPriority)  
{ 
    return E_NOTIMPL; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::OnLowResource 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::OnLowResource(DWORD dwReserved) 
{ 
    return E_NOTIMPL; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::OnProgress 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::OnProgress 
    (ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText) 
{ 
    return S_OK; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::OnStopBinding 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::OnStopBinding 
    (HRESULT hrStatus, LPCWSTR pszError)  
{ 
    // Release the binding, which will eventually release the callback object. 
    m_pBinding->Release(); 
    m_pBinding = NULL; 
 
    // Notify the client that we are done. 
    m_CBParam.CallbackType = REQUESTCB_DONE; 
    m_CBParam.hrRequest = hrStatus; 
    (*(m_pParam->pfnRequestCB)) (&m_CBParam); 
     
    return S_OK; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::GetBindInfo 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::GetBindInfo 
    (DWORD* pgrfBINDF, BINDINFO* pbindInfo) 
{ 
    *pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA; 
    *pgrfBINDF |= BINDF_GETNEWESTVERSION; 
    pbindInfo->cbSize = sizeof(BINDINFO); 
    pbindInfo->szExtraInfo = NULL; 
    ZeroMemory (&pbindInfo->stgmedData, sizeof(STGMEDIUM)); 
    pbindInfo->grfBindInfoF = 0; 
    pbindInfo->dwBindVerb = BINDVERB_GET; 
    pbindInfo->szCustomVerb = NULL; 
    return S_OK; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::OnDataAvailable 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::OnDataAvailable 
    (DWORD grfBSCF, DWORD dwSize, FORMATETC* pfmtetc, STGMEDIUM* pstgmed)  
{ 
    if (!m_pstm && pstgmed->tymed == TYMED_ISTREAM) 
    { 
        m_pstm = pstgmed->pstm; 
        m_pstm->AddRef(); 
        m_CBParam.CallbackType = REQUESTCB_DATA; 
    } 
 
    // Check for multi-part response. 
    if (m_pszRangeDelimiter) 
       return ParseMultiPartBuffer (grfBSCF & BSCF_LASTDATANOTIFICATION); 
 
    HRESULT hrRead; 
    DWORD cbActual; 
 
    do // until hrRead != S_OK 
    { 
        // Read some more data. 
        hrRead = m_pstm->Read (BufBeg(), BufSize(), &cbActual); 
 
        // Notify the client we got some data. 
        if (cbActual) 
        { 
            m_CBParam.dwDataOffset = m_dwOffset; 
            m_CBParam.lpDataBuffer = BufBeg(); 
            m_CBParam.cbDataLength = cbActual; 
            if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam)) 
            { 
                 // Client wants to stop. 
                 m_pBinding->Abort(); 
                 return S_OK; 
            } 
        }    
 
        m_dwOffset += cbActual; 
         
    } while (hrRead == S_OK); 
 
    return (hrRead == S_FALSE || hrRead == E_PENDING)? S_OK : hrRead; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::OnObjectAvailable 
// --------------------------------------------------------------------------- 
STDMETHODIMP CUrlmonCallback::OnObjectAvailable(REFIID riid, IUnknown* punk)  
{ 
    return E_NOTIMPL; 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::ParseMultiPartHeader 
// --------------------------------------------------------------------------- 
BOOL CUrlmonCallback::ParseMultiPartHeader (void) 
{ 
    // multi-part boundary string literals. 
    static char szContentType[]  = "Content-Type: "; 
    static char szContentRange[] = "Content-Range: bytes "; 
 
// Some macros for prettiness... 
#define GrokStr(str) if (!BufScanStr (str, sizeof(str)-1)) return FALSE; 
#define GrokInt(pint, delim) if (!BufScanInt (pint,delim)) return FALSE; 
 
// Check for presence of boundary. 
    if (m_dwRangeBeg > 0) // must not be first range 
    GrokStr (szLF);   // will also detect a CR-LF 
if (!BufScanStr (m_pszRangeDelimiter, m_cchRangeDelimiter)) 
    return FALSE; 
 
// Check for end boundary. 
if 
(    m_pbDataBeg + 2 <= m_pbDataEnd 
&& m_pbDataBeg[0] == '-' 
&& m_pbDataBeg[1] == '-' 
) 
{ 
    m_dwRangeBeg = 0; 
    m_dwRangeEnd = 0; 
    return TRUE; 
    } 
     
// Scan content type and data range. 
GrokStr (szLF); 
GrokStr (szContentType); 
GrokStr (szLF); 
GrokStr (szContentRange); 
    GrokInt (&m_dwRangeBeg, '-'); 
    GrokInt (&m_dwRangeEnd, '/'); 
    GrokStr (szLF); 
    GrokStr (szLF); 
     
    // Range end is inclusive; make it exclusive. 
    m_dwRangeEnd++;  
    return TRUE; 
 
#undef GrokStr 
#undef GrokInt 
} 
 
// --------------------------------------------------------------------------- 
// CUrlmonCallback::ParseMultiPartBuffer 
// --------------------------------------------------------------------------- 
HRESULT CUrlmonCallback::ParseMultiPartBuffer (BOOL fLastCall) 
{ 
    HRESULT hrRead; 
    DWORD cbActual; 
 
    do // until hrRead != S_OK 
    { 
        // Read data and append to buffer. 
        hrRead = m_pstm->Read(m_pbDataEnd, BufSpace(), &cbActual); 
        m_pbDataEnd += cbActual; 
 
        // Dispatch to current state handler. 
        if (m_dwRangeBeg != m_dwRangeEnd) 
            goto process_data; 
        else 
            goto parse_header; 
             
parse_header: 
 
        // If download not at EOF, don't try to parse multi-part 
        // boundary if any chance it would be split across buffers. 
        if (hrRead != S_FALSE && m_pbDataBeg > m_pbDataEnd - BOUNDARY_MAXSIZE) 
        { 
            BufShift(); 
            continue; 
        } 
 
        // Attempt to parse the multi-part boundary. 
        if (!ParseMultiPartHeader()) 
            break; 
                
        // Check if we got termination boundary. 
        if (m_dwRangeBeg == m_dwRangeEnd) 
            break; 
 
        goto process_data; 
 
process_data: 
 
        // Calculate amount of data to report. 
        DWORD cbData   = m_pbDataEnd  - m_pbDataBeg; 
        DWORD cbRange  = m_dwRangeEnd - m_dwRangeBeg; 
        DWORD cbLength = min (cbData, cbRange); 
 
        if (cbLength) 
        { 
            // Call back the client with more data. 
            m_CBParam.dwDataOffset = m_dwRangeBeg; 
            m_CBParam.lpDataBuffer = m_pbDataBeg; 
            m_CBParam.cbDataLength = cbLength; 
            if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam)) 
            { 
                 // Client wants to stop. 
                 m_pBinding->Abort(); 
                 break; 
            } 
            m_dwRangeBeg += cbLength; 
        } 
 
        // Adjust the buffer depending on next state. 
        if (m_dwRangeBeg == m_dwRangeEnd) // (cbLength == cbRange) 
        { 
            // Consume the data and look for next header. 
            m_pbDataBeg += cbLength; 
            goto parse_header; 
        } 
        else 
        { 
            // Reset buffer and get some more data. 
            BufReset(); 
            continue; 
        } 
 
    } while (hrRead == S_OK); 
 
    return (hrRead == S_FALSE || hrRead == E_PENDING)? S_OK : hrRead; 
}