home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1996 December / PCWKCD1296.iso / vjplusb / activex / inetsdk / samples / range / callback.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-24  |  18.2 KB  |  578 lines

  1. // ===========================================================================
  2. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
  3. // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  4. // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  5. // PARTICULAR PURPOSE.
  6. //
  7. // Copyright 1996 Microsoft Corporation.  All Rights Reserved.
  8. // ===========================================================================
  9. #include <urlmon.h>
  10. #include <wininet.h>
  11. #include "callback.hpp"
  12.  
  13. #define BOUNDARY_MAXSIZE 500 
  14. #define RECV_BUF_SIZE   8192
  15.  
  16. static char szCRLF[] = "\r\n";
  17.  
  18. // ---------------------------------------------------------------------------
  19. // CUrlmonCallback::CUrlmonCallback
  20. // ---------------------------------------------------------------------------
  21. CUrlmonCallback::CUrlmonCallback (PHTTP_REQUEST_PARAM pParam)
  22. {
  23.     m_pBinding = NULL;
  24.     m_pstm = NULL;
  25.     m_cRef = 0;
  26.  
  27.     m_pParam = pParam;
  28.     
  29.     m_CBParam.cbStruct = sizeof(m_CBParam);
  30.     m_CBParam.dwRequestCtx = m_pParam->dwRequestCtx;
  31.  
  32.     m_dwOffset = 0;
  33.     m_dwResponseCode = 0;
  34.     m_pszRangeDelimiter = NULL;
  35. }
  36.  
  37. // ---------------------------------------------------------------------------
  38. // CUrlmonCallback::~CUrlmonCallback
  39. // ---------------------------------------------------------------------------
  40. CUrlmonCallback::~CUrlmonCallback()
  41. {
  42.     if (m_pstm)
  43.         m_pstm->Release();
  44.     if (m_pBinding)
  45.         m_pBinding->Release();
  46.     if (m_pszRangeDelimiter)
  47.         LocalFree ((HLOCAL) m_pszRangeDelimiter);
  48. }
  49.  
  50. // ---------------------------------------------------------------------------
  51. // CUrlmonCallback::QueryInterface
  52. // ---------------------------------------------------------------------------
  53. STDMETHODIMP CUrlmonCallback::QueryInterface(REFIID riid, void** ppv)
  54.     if(IsEqualGUID(riid,IID_IUnknown))
  55.         *ppv = (IUnknown *) (IBindStatusCallback *) this;
  56.     else if (IsEqualGUID(riid,IID_IBindStatusCallback))
  57.         *ppv = (IBindStatusCallback *) this;
  58.     else if (IsEqualGUID(riid, IID_IHttpNegotiate))
  59.         *ppv = (IHttpNegotiate *) this;
  60.     else
  61.         *ppv = NULL;
  62.  
  63.     if (!*ppv)
  64.         return E_NOINTERFACE;
  65.     
  66.     // Increment our reference count before we hand out our interface
  67.     ((LPUNKNOWN)*ppv)->AddRef();
  68.     return S_OK;
  69.  
  70. }
  71.  
  72. // ---------------------------------------------------------------------------
  73. // CUrlmonCallback::BeginningTransaction
  74. // ---------------------------------------------------------------------------
  75. STDMETHODIMP
  76. CUrlmonCallback::BeginningTransaction (LPCWSTR szURL,
  77.     LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *ppszAdditionalHeaders)
  78. {
  79.     static char szRangeHeader[] = "Range: bytes=";
  80.     static char szUnlessHeader[] = "Unless-Modified-Since: ";
  81.  
  82.     HRESULT hr = S_OK;
  83.     PSTR pszHeader = NULL;
  84.     
  85.     // Don't add any headers if not a range request.
  86.     DWORD cRanges = m_pParam->cRanges;
  87.     if (!cRanges)
  88.     {
  89.         *ppszAdditionalHeaders = NULL;
  90.         goto done;
  91.     }
  92.  
  93.     PHTTP_REQUEST_RANGE pRanges;
  94.     pRanges = m_pParam->pRanges;
  95.  
  96.     // Allocate ANSI buffer.
  97.     DWORD cbHeader;
  98.     cbHeader = sizeof(szRangeHeader) + 20 * cRanges + 2;
  99.     if (m_pParam->pstUnlessModifiedSince)
  100.        cbHeader += sizeof(szUnlessHeader) + INTERNET_RFC1123_BUFSIZE + 2;
  101.     pszHeader = (PSTR) LocalAlloc (LMEM_FIXED, cbHeader);
  102.     if (!pszHeader)
  103.     {
  104.         hr = E_OUTOFMEMORY;
  105.         goto done;
  106.     }
  107.  
  108.     // Format the read range request header.
  109.     UINT cchHeader;
  110.     cchHeader = wsprintf (pszHeader, "%s", szRangeHeader);
  111.  
  112.     // Add the ranges.
  113.     while (cRanges--)
  114.     {
  115.         if (pRanges->dwSize)
  116.         {
  117.             // Format range, end value is inclusive.
  118.             cchHeader += wsprintf (pszHeader + cchHeader, "%d-%d",
  119.                 pRanges->dwOffset, pRanges->dwOffset + pRanges->dwSize - 1);
  120.         }
  121.         else
  122.         {
  123.             // Format range to end of file.
  124.             cchHeader += wsprintf (pszHeader + cchHeader, "%d-",
  125.                 pRanges->dwOffset);
  126.         }
  127.             
  128.         pRanges++;
  129.         if (cRanges)
  130.            cchHeader += wsprintf (pszHeader + cchHeader, ", ");
  131.         else
  132.            cchHeader += wsprintf (pszHeader + cchHeader, szCRLF);
  133.     }
  134.  
  135.     if (m_pParam->pstUnlessModifiedSince)
  136.     {
  137.         // Add unless-modified-since qualifier.
  138.         cchHeader += wsprintf (pszHeader + cchHeader, szUnlessHeader);
  139.         if (!InternetTimeFromSystemTime
  140.         (
  141.             m_pParam->pstUnlessModifiedSince,
  142.             INTERNET_RFC1123_FORMAT,
  143.             pszHeader + cchHeader,
  144.             INTERNET_RFC1123_BUFSIZE
  145.         ))
  146.         {
  147.             hr = E_FAIL;
  148.             goto done;
  149.         }
  150.         
  151.         cchHeader += lstrlen (pszHeader + cchHeader);
  152.         cchHeader += wsprintf (pszHeader + cchHeader, szCRLF);
  153.     }
  154.  
  155.     cchHeader++; // for NULL termination
  156.  
  157.     // Allocate Unicode buffer.
  158.     *ppszAdditionalHeaders = (WCHAR *) CoTaskMemAlloc (sizeof(WCHAR) * cchHeader);
  159.     if (*ppszAdditionalHeaders)
  160.     {
  161.         MultiByteToWideChar (CP_ACP, 0, pszHeader, -1, *ppszAdditionalHeaders,
  162.             sizeof(WCHAR) * cchHeader);
  163.     }
  164.  
  165.     hr = *ppszAdditionalHeaders? S_OK : E_OUTOFMEMORY;
  166.  
  167. done:
  168.     if (pszHeader)
  169.         LocalFree (pszHeader);
  170.     return hr;
  171. }
  172.  
  173. // ---------------------------------------------------------------------------
  174. // CUrlmonCallback::OnResponse
  175. // ---------------------------------------------------------------------------
  176. STDMETHODIMP CUrlmonCallback::OnResponse
  177. (
  178.     DWORD   dwResponseCode, 
  179.     LPCWSTR szResponseHeaders,
  180.     LPCWSTR szRequestHeaders,
  181.     LPWSTR  *pszAdditionalRequestHeaders
  182. )
  183. {
  184.     // Get the HttpQueryInfo wrapper object.
  185.     IWinInetHttpInfo *pHttpInfo;
  186.     HRESULT hr = m_pBinding->QueryInterface
  187.         (IID_IWinInetHttpInfo, (void **) &pHttpInfo);
  188.     if (FAILED(hr))
  189.         return E_FAIL;
  190.  
  191.     // Save the response code.
  192.     m_dwResponseCode = dwResponseCode;
  193.     m_CBParam.dwResponseCode = dwResponseCode;
  194.  
  195.     DWORD cbBuf;
  196.  
  197.     // Check for partial response.
  198.     if (dwResponseCode == 206)
  199.     {
  200.        // Server responded with byte ranges, ergo must support them.
  201.        m_CBParam.fdwRequestFlags = HTTP_REQUEST_ACCEPT_RANGES;
  202.  
  203.        // Look for multi-part delimiter embedded in content type.
  204.        const static char szMultiPart[] = "multipart/x-byteranges; boundary";
  205.        const static DWORD cbMultiPart = sizeof(szMultiPart);
  206.  
  207.        // Get length of buffer to hold content type.
  208.        DWORD cbContentType = 0;
  209.        pHttpInfo->QueryInfo
  210.           (HTTP_QUERY_CONTENT_TYPE, NULL, &cbContentType, NULL, 0);
  211.  
  212.        if (cbContentType > cbMultiPart + 1)
  213.        {
  214.             // Content type could be big enough to embed a delimiter.
  215.             m_pszRangeDelimiter = (PSTR) LocalAlloc (LMEM_FIXED, cbContentType);
  216.             if (!m_pszRangeDelimiter)
  217.                 return E_OUTOFMEMORY;
  218.  
  219.             if (S_OK != pHttpInfo->QueryInfo (HTTP_QUERY_CONTENT_TYPE,
  220.                  m_pszRangeDelimiter, &cbContentType, NULL, 0))
  221.             {
  222.                  return E_FAIL;
  223.             } 
  224.             
  225.             // Split the string at the '='
  226.             m_pszRangeDelimiter[cbMultiPart - 1] = 0;
  227.             if (lstrcmpi (m_pszRangeDelimiter, szMultiPart))
  228.             {
  229.                 // Response must not be multi-part.
  230.                 LocalFree ((HLOCAL) m_pszRangeDelimiter);
  231.                 m_pszRangeDelimiter = NULL;
  232.             }
  233.             else
  234.             {
  235.                 m_cchRangeDelimiter =
  236.                     lstrlen (m_pszRangeDelimiter + cbMultiPart);
  237.  
  238.                 // Shift the delimiter to offset 2 of string.
  239.                 MoveMemory
  240.                 (
  241.                   m_pszRangeDelimiter + 2, // +2 for prefix
  242.                   m_pszRangeDelimiter + cbMultiPart,
  243.                   m_cchRangeDelimiter + 1  // +1 for null
  244.                 );    
  245.  
  246.                 // Prefix delimiter with "--"
  247.                 m_pszRangeDelimiter[0] = '-';
  248.                 m_pszRangeDelimiter[1] = '-';
  249.                 m_cchRangeDelimiter += 2;
  250.  
  251.                 // Initialize range boundaries.
  252.                 m_dwRangeBeg = 0;
  253.                 m_dwRangeEnd   = 0;
  254.             }
  255.             
  256.         } // end if (cbContentType > cbMultiPart + 1)
  257.  
  258.         // Adjust the offset if we have a single range
  259.         if (!m_pszRangeDelimiter)
  260.             m_dwOffset = m_pParam->pRanges[0].dwOffset;
  261.     }
  262.     
  263.     else // if (dwResponseCode != 206)
  264.     {
  265.         // Check if ranges are supported.
  266.         static char szBytes[] = "bytes";
  267.         char szBuf[sizeof(szBytes)];
  268.         cbBuf = sizeof(szBytes);
  269.         hr = pHttpInfo->QueryInfo
  270.             (HTTP_QUERY_ACCEPT_RANGES, szBuf, &cbBuf, NULL, 0);
  271.         if (SUCCEEDED(hr) && !lstrcmpi (szBytes, szBuf))
  272.             m_CBParam.fdwRequestFlags = HTTP_REQUEST_ACCEPT_RANGES;
  273.         else
  274.             m_CBParam.fdwRequestFlags = 0;
  275.     }
  276.  
  277.     // Get last modified time.
  278.     SYSTEMTIME stLastModified;
  279.     cbBuf = sizeof(stLastModified);
  280.     hr = pHttpInfo->QueryInfo
  281.     (
  282.         HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED,
  283.         &stLastModified, &cbBuf, NULL, 0
  284.     );
  285.     m_CBParam.pstLastModified = hr==S_OK? &stLastModified : NULL;
  286.                 
  287.     // Get content length.
  288.     m_CBParam.dwContentLength = 0;
  289.     if (dwResponseCode != 206)
  290.     {   
  291.         cbBuf = sizeof(m_CBParam.dwContentLength);
  292.         hr = pHttpInfo->QueryInfo
  293.         (
  294.             HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH,
  295.             &m_CBParam.dwContentLength, &cbBuf, NULL, 0
  296.         );
  297.     }
  298.     
  299.     pHttpInfo->Release;
  300.  
  301.     // Inform the client the request is started.
  302.     m_CBParam.CallbackType = REQUESTCB_STARTED;
  303.     if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam))
  304.     {
  305.          // Client wants to stop.
  306.          m_pBinding->Abort();
  307.          return S_OK;
  308.     }
  309.     
  310.     return S_OK;
  311. }
  312.  
  313. // ---------------------------------------------------------------------------
  314. // CUrlmonCallback::OnStartBinding
  315. // ---------------------------------------------------------------------------
  316. STDMETHODIMP CUrlmonCallback::OnStartBinding
  317.     (DWORD grfBSCOption, IBinding* pBinding)
  318. {
  319.     if (pBinding != NULL)
  320.     {
  321.          m_pBinding = pBinding;
  322.          m_pBinding->AddRef();
  323.     }
  324.     
  325.     // Initialize receive buffer.
  326.     if (!BufAlloc(RECV_BUF_SIZE))
  327.         return E_OUTOFMEMORY;
  328.         
  329.     return S_OK;
  330. }
  331.  
  332. // ---------------------------------------------------------------------------
  333. // CUrlmonCallback::GetPriority
  334. // ---------------------------------------------------------------------------
  335. STDMETHODIMP CUrlmonCallback::GetPriority(LONG* pnPriority) 
  336. {
  337.     return E_NOTIMPL;
  338. }
  339.  
  340. // ---------------------------------------------------------------------------
  341. // CUrlmonCallback::OnLowResource
  342. // ---------------------------------------------------------------------------
  343. STDMETHODIMP CUrlmonCallback::OnLowResource(DWORD dwReserved)
  344. {
  345.     return E_NOTIMPL;
  346. }
  347.  
  348. // ---------------------------------------------------------------------------
  349. // CUrlmonCallback::OnProgress
  350. // ---------------------------------------------------------------------------
  351. STDMETHODIMP CUrlmonCallback::OnProgress
  352.     (ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
  353. {
  354.     return S_OK;
  355. }
  356.  
  357. // ---------------------------------------------------------------------------
  358. // CUrlmonCallback::OnStopBinding
  359. // ---------------------------------------------------------------------------
  360. STDMETHODIMP CUrlmonCallback::OnStopBinding
  361.     (HRESULT hrStatus, LPCWSTR pszError) 
  362. {
  363.     // Release the binding, which will eventually release the callback object.
  364.     m_pBinding->Release();
  365.     m_pBinding = NULL;
  366.  
  367.     // Notify the client that we are done.
  368.     m_CBParam.CallbackType = REQUESTCB_DONE;
  369.     m_CBParam.hrRequest = hrStatus;
  370.     (*(m_pParam->pfnRequestCB)) (&m_CBParam);
  371.     
  372.     return S_OK;
  373. }
  374.  
  375. // ---------------------------------------------------------------------------
  376. // CUrlmonCallback::GetBindInfo
  377. // ---------------------------------------------------------------------------
  378. STDMETHODIMP CUrlmonCallback::GetBindInfo
  379.     (DWORD* pgrfBINDF, BINDINFO* pbindInfo)
  380. {
  381.     *pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
  382.     *pgrfBINDF |= BINDF_GETNEWESTVERSION;
  383.     pbindInfo->cbSize = sizeof(BINDINFO);
  384.     pbindInfo->szExtraInfo = NULL;
  385.     ZeroMemory (&pbindInfo->stgmedData, sizeof(STGMEDIUM));
  386.     pbindInfo->grfBindInfoF = 0;
  387.     pbindInfo->dwBindVerb = BINDVERB_GET;
  388.     pbindInfo->szCustomVerb = NULL;
  389.     return S_OK;
  390. }
  391.  
  392. // ---------------------------------------------------------------------------
  393. // CUrlmonCallback::OnDataAvailable
  394. // ---------------------------------------------------------------------------
  395. STDMETHODIMP CUrlmonCallback::OnDataAvailable
  396.     (DWORD grfBSCF, DWORD dwSize, FORMATETC* pfmtetc, STGMEDIUM* pstgmed) 
  397. {
  398.     if (!m_pstm && pstgmed->tymed == TYMED_ISTREAM)
  399.     {
  400.         m_pstm = pstgmed->pstm;
  401.         m_pstm->AddRef();
  402.         m_CBParam.CallbackType = REQUESTCB_DATA;
  403.     }
  404.  
  405.     // Check for multi-part response.
  406.     if (m_pszRangeDelimiter)
  407.        return ParseMultiPartBuffer (grfBSCF & BSCF_LASTDATANOTIFICATION);
  408.  
  409.     HRESULT hrRead;
  410.     DWORD cbActual;
  411.  
  412.     do // until hrRead != S_OK
  413.     {
  414.         // Read some more data.
  415.         hrRead = m_pstm->Read (BufBeg(), BufSize(), &cbActual);
  416.  
  417.         // Notify the client we got some data.
  418.         if (cbActual)
  419.         {
  420.             m_CBParam.dwDataOffset = m_dwOffset;
  421.             m_CBParam.lpDataBuffer = BufBeg();
  422.             m_CBParam.cbDataLength = cbActual;
  423.             if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam))
  424.             {
  425.                  // Client wants to stop.
  426.                  m_pBinding->Abort();
  427.                  return S_OK;
  428.             }
  429.         }   
  430.  
  431.         m_dwOffset += cbActual;
  432.         
  433.     } while (hrRead == S_OK);
  434.  
  435.     return (hrRead == S_FALSE || hrRead == E_PENDING)? S_OK : hrRead;
  436. }
  437.  
  438. // ---------------------------------------------------------------------------
  439. // CUrlmonCallback::OnObjectAvailable
  440. // ---------------------------------------------------------------------------
  441. STDMETHODIMP CUrlmonCallback::OnObjectAvailable(REFIID riid, IUnknown* punk) 
  442. {
  443.     return E_NOTIMPL;
  444. }
  445.  
  446. // ---------------------------------------------------------------------------
  447. // CUrlmonCallback::ParseMultiPartHeader
  448. // ---------------------------------------------------------------------------
  449. BOOL CUrlmonCallback::ParseMultiPartHeader (void)
  450. {
  451.     // multi-part boundary string literals.
  452.     static char szLF[] = "\n";
  453.     static char szContentType[]  = "Content-Type: ";
  454.     static char szContentRange[] = "Content-Range: bytes ";
  455.  
  456. // Some macros for prettiness...
  457. #define GrokStr(str) if (!BufScanStr (str, sizeof(str)-1)) return FALSE;
  458. #define GrokInt(pint, delim) if (!BufScanInt (pint,delim)) return FALSE;
  459.  
  460.     // Check for presence of boundary.
  461.     if (m_dwRangeBeg > 0) // must not be first range
  462.         GrokStr (szLF);   // will also detect a CR-LF
  463.     if (!BufScanStr (m_pszRangeDelimiter, m_cchRangeDelimiter))
  464.         return FALSE;
  465.  
  466.     // Check for end boundary.
  467.     if
  468.     (    m_pbDataBeg + 2 <= m_pbDataEnd
  469.         && m_pbDataBeg[0] == '-'
  470.         && m_pbDataBeg[1] == '-'
  471.     )
  472.     {
  473.         m_dwRangeBeg = 0;
  474.         m_dwRangeEnd = 0;
  475.         return TRUE;
  476.     }
  477.     
  478.     // Scan content type and data range.
  479.     GrokStr (szLF);
  480.     GrokStr (szContentType);
  481.     GrokStr (szLF);
  482.     GrokStr (szContentRange);
  483.     GrokInt (&m_dwRangeBeg, '-');
  484.     GrokInt (&m_dwRangeEnd, '/');
  485.     GrokStr (szLF);
  486.     GrokStr (szLF);
  487.     
  488.     // Range end is inclusive; make it exclusive.
  489.     m_dwRangeEnd++; 
  490.     return TRUE;
  491.  
  492. #undef GrokStr
  493. #undef GrokInt
  494. }
  495.  
  496. // ---------------------------------------------------------------------------
  497. // CUrlmonCallback::ParseMultiPartBuffer
  498. // ---------------------------------------------------------------------------
  499. HRESULT CUrlmonCallback::ParseMultiPartBuffer (BOOL fLastCall)
  500. {
  501.     HRESULT hrRead;
  502.     DWORD cbActual;
  503.  
  504.     do // until hrRead != S_OK
  505.     {
  506.         // Read data and append to buffer.
  507.         hrRead = m_pstm->Read(m_pbDataEnd, BufSpace(), &cbActual);
  508.         m_pbDataEnd += cbActual;
  509.  
  510.         // Dispatch to current state handler.
  511.         if (m_dwRangeBeg != m_dwRangeEnd)
  512.             goto process_data;
  513.         else
  514.             goto parse_header;
  515.             
  516. parse_header:
  517.  
  518.         // If download not at EOF, don't try to parse multi-part
  519.         // boundary if any chance it would be split across buffers.
  520.         if (hrRead != S_FALSE && m_pbDataBeg > m_pbDataEnd - BOUNDARY_MAXSIZE)
  521.         {
  522.             BufShift();
  523.             continue;
  524.         }
  525.  
  526.         // Attempt to parse the multi-part boundary.
  527.         if (!ParseMultiPartHeader())
  528.             break;
  529.                
  530.         // Check if we got termination boundary.
  531.         if (m_dwRangeBeg == m_dwRangeEnd)
  532.             break;
  533.  
  534.         goto process_data;
  535.  
  536. process_data:
  537.  
  538.         // Calculate amount of data to report.
  539.         DWORD cbData   = m_pbDataEnd  - m_pbDataBeg;
  540.         DWORD cbRange  = m_dwRangeEnd - m_dwRangeBeg;
  541.         DWORD cbLength = min (cbData, cbRange);
  542.  
  543.         if (cbLength)
  544.         {
  545.             // Call back the client with more data.
  546.             m_CBParam.dwDataOffset = m_dwRangeBeg;
  547.             m_CBParam.lpDataBuffer = m_pbDataBeg;
  548.             m_CBParam.cbDataLength = cbLength;
  549.             if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam))
  550.             {
  551.                  // Client wants to stop.
  552.                  m_pBinding->Abort();
  553.                  break;
  554.             }
  555.             m_dwRangeBeg += cbLength;
  556.         }
  557.  
  558.         // Adjust the buffer depending on next state.
  559.         if (m_dwRangeBeg == m_dwRangeEnd) // (cbLength == cbRange)
  560.         {
  561.             // Consume the data and look for next header.
  562.             m_pbDataBeg += cbLength;
  563.             goto parse_header;
  564.         }
  565.         else
  566.         {
  567.             // Reset buffer and get some more data.
  568.             BufReset();
  569.             continue;
  570.         }
  571.  
  572.     } while (hrRead == S_OK);
  573.  
  574.     return (hrRead == S_FALSE || hrRead == E_PENDING)? S_OK : hrRead;
  575. }
  576.  
  577.