FireBreath  1.4.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Pages
axstream_impl.cpp
1 /**********************************************************\
2 Original Author: Matthias (nitrogenycs)
3 
4 Created: Feb 28, 2010
5 License: Dual license model; choose one of two:
6  New BSD License
7  http://www.opensource.org/licenses/bsd-license.php
8  - or -
9  GNU Lesser General Public License, version 2.1
10  http://www.gnu.org/licenses/lgpl-2.1.html
11 
12 Copyright 2010 Richard Bateman, Firebreath development team
13 \**********************************************************/
14 
15 // includes parts of Microsoft examples:
16 // http://support.microsoft.com/kb/223500
17 // http://support.microsoft.com/kb/165800
18 
19 #include "axstream_impl.h"
20 #include "axstream.h"
21 #include <tchar.h>
22 #include <sstream>
23 #include "wininet.h"
24 #include "utf8_tools.h"
25 #include <boost\smart_ptr\scoped_array.hpp>
26 #include <boost\smart_ptr\shared_array.hpp>
27 #include "precompiled_headers.h" // On windows, everything above this line in PCH
28 
29 using namespace FB;
30 using namespace FB::ActiveX;
31 
32 #define ODS(x)
33 
34 // ===========================================================================
35 // ActiveXBindStatusCallback Implementation
36 // ===========================================================================
37 
38 // ---------------------------------------------------------------------------
39 // %%Function: ActiveXBindStatusCallback::ActiveXBindStatusCallback
40 // ---------------------------------------------------------------------------
41 ActiveXBindStatusCallback::ActiveXBindStatusCallback() :
42  m_pbinding(0), m_pstm(0), m_cRef(1), m_cbOld(0), m_dwAction( BINDVERB_GET ), m_fRedirect( FALSE ), m_transactionStarted( false ),
43  m_hDataToPost(NULL), m_cbDataToPost(0)
44 {
45 }
46 
47 ActiveXBindStatusCallback::~ActiveXBindStatusCallback()
48 {
49  if (m_hDataToPost)
50  {
51  ::GlobalFree(m_hDataToPost);
52  m_hDataToPost = NULL;
53  }
54 }
55 
56 // ---------------------------------------------------------------------------
57 // %%Function: ActiveXBindStatusCallback::Create
58 // ---------------------------------------------------------------------------
59 HRESULT ActiveXBindStatusCallback::Create(ActiveXBindStatusCallback** ppBindStatusCallback, ActiveXStreamRequestPtr request)
60 {
61  HRESULT hr;
62  ActiveXBindStatusCallback* pBindStatusCallback;
63 
64  if (!ppBindStatusCallback)
65  {
66  return E_POINTER;
67  }
68 
69  *ppBindStatusCallback = NULL;
70 
71  pBindStatusCallback = new ActiveXBindStatusCallback();
72  if (!pBindStatusCallback)
73  {
74  return E_OUTOFMEMORY;
75  }
76 
77  hr = pBindStatusCallback->Init(request);
78  if (FAILED(hr))
79  {
80  delete pBindStatusCallback;
81  return hr;
82  }
83 
84  *ppBindStatusCallback = pBindStatusCallback;
85  return NOERROR;
86 }
87 
88 // ---------------------------------------------------------------------------
89 // %%Function: ActiveXBindStatusCallback::Init
90 // ---------------------------------------------------------------------------
91 HRESULT ActiveXBindStatusCallback::Init(ActiveXStreamRequestPtr request)
92 {
93  HRESULT hr = NOERROR;
94  m_request = request;
95 
96  if( m_request->stream
97  && !m_request->stream->getVerbData().empty() )
98  {
99  // Is a post request
100  m_dwAction = BINDVERB_POST;
101  hr = InitPostData(m_request->stream->getVerbData().c_str());
102  }
103 
104  m_dwAction = m_request->stream->getVerbData().empty()? BINDVERB_GET : BINDVERB_POST;
105  return hr;
106 }
107 
108 HRESULT ActiveXBindStatusCallback::InitPostData(const char* szData)
109 {
110  if (m_hDataToPost)
111  {
112  // We're already in use and don't support recycling the same instance
113  // Some other client may have a reference on us.
114  // If we were to free this data, the client might crash dereferencing the handle
115  return E_FAIL;
116  }
117 
118  if (szData)
119  {
120  // MSINTERNAL: See CINetHttp::INetAsyncSendRequest (cnethttp.cxx) that URLMON calls CINetHttp::GetDataToSend() followed by a call to WININET's HttpSendRequest(). GetDataToSend essentially pulls the data out of the BINDINFO that URLMON has cached away when it calls the host's implementation of IBindStatusCallback::GetBindInfo().
121  // MSINTERNAL: It doesn't attempt to lock down the HGLOBAL at all, so we need to allocated GMEM_FIXED
122  m_cbDataToPost = ::lstrlenA(szData);
123  m_hDataToPost = ::GlobalAlloc(GPTR, m_cbDataToPost+1); // GMEM_MOVEABLE won't work because URLMON doesn't attempt GlobalLock before dereferencing
124  if (!m_hDataToPost)
125  {
126  return E_OUTOFMEMORY;
127  }
128 
129  // the memory was allocate fixed, so no need to lock it down
130  ::lstrcpyA((char*)m_hDataToPost, szData);
131  }
132 
133  return NOERROR;
134 }
135 
136 // ---------------------------------------------------------------------------
137 // %%Function: ActiveXBindStatusCallback::QueryInterface
138 // ---------------------------------------------------------------------------
139  STDMETHODIMP
140 ActiveXBindStatusCallback::QueryInterface(REFIID riid, void** ppv)
141 {
142  *ppv = NULL;
143 
144  if (riid==IID_IUnknown || riid==IID_IBindStatusCallback)
145  {
146  *ppv = (IBindStatusCallback*)this;
147  AddRef();
148  return S_OK;
149  }
150  else if (riid==IID_IHttpNegotiate)
151  {
152  *ppv = (IHttpNegotiate*)this;
153  AddRef();
154  return S_OK;
155  }
156  else
157  {
158  return E_NOINTERFACE;
159  }
160 
161 } // ActiveXBindStatusCallback::QueryInterface
162 
163 // ---------------------------------------------------------------------------
164 // %%Function: ActiveXBindStatusCallback::OnStartBinding
165 // ---------------------------------------------------------------------------
166  STDMETHODIMP
167 ActiveXBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding* pbinding)
168 {
169  if (NULL != m_pbinding)
170  {
171  m_pbinding->Release();
172  }
173 
174  // Cache the URLMON-provided IBinding interface so that we can control the download
175  m_pbinding = pbinding;
176 
177  if (m_pbinding != NULL)
178  {
179  m_pbinding->AddRef();
180  //SetStatusText(_T("Starting to bind..."));
181  }
182 
183  return S_OK;
184 } // ActiveXBindStatusCallback::OnStartBinding
185 
186 // ---------------------------------------------------------------------------
187 // %%Function: ActiveXBindStatusCallback::GetPriority
188 // ---------------------------------------------------------------------------
189  STDMETHODIMP
190 ActiveXBindStatusCallback::GetPriority(LONG* pnPriority)
191 {
192  return E_NOTIMPL;
193 } // ActiveXBindStatusCallback::GetPriority
194 
195 // ---------------------------------------------------------------------------
196 // %%Function: ActiveXBindStatusCallback::OnLowResource
197 // ---------------------------------------------------------------------------
198  STDMETHODIMP
199 ActiveXBindStatusCallback::OnLowResource(DWORD dwReserved)
200 {
201  return E_NOTIMPL;
202 } // ActiveXBindStatusCallback::OnLowResource
203 
204 // ---------------------------------------------------------------------------
205 // %%Function: ActiveXBindStatusCallback::OnProgress
206 // ---------------------------------------------------------------------------
207 STDMETHODIMP ActiveXBindStatusCallback::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
208 {
209  //if ( ulProgressMax && m_request->stream ) m_request->stream->length = ulProgressMax;
210 
211  // for more see here http://msdn.microsoft.com/en-us/library/ms775133(VS.85).aspx
212  switch (ulStatusCode)
213  {
214  case BINDSTATUS_REDIRECTING:
215  ODS("Server redirecting client\n");
216  m_fRedirect = TRUE;
217  break;
218  case BINDSTATUS_FINDINGRESOURCE:
219  ODS("Finding resource\n");
220  break;
221  case BINDSTATUS_CONNECTING:
222  ODS("Connecting...\n");
223  break;
224  case BINDSTATUS_BEGINDOWNLOADDATA:
225  ODS("Beginning to download data...\n");
226  break;
227  case BINDSTATUS_DOWNLOADINGDATA:
228  ODS("Downloading data...\n");
229  break;
230  case BINDSTATUS_ENDDOWNLOADDATA:
231  ODS("Ending data download...\n");
232  break;
233  case BINDSTATUS_BEGINDOWNLOADCOMPONENTS:
234  ODS("Beginning to download components...\n");
235  break;
236  case BINDSTATUS_INSTALLINGCOMPONENTS:
237  ODS("Installing components...\n");
238  break;
239  case BINDSTATUS_ENDDOWNLOADCOMPONENTS:
240  ODS("Ending component download...\n");
241  break;
242  case BINDSTATUS_USINGCACHEDCOPY:
243  ODS("Using cached copy...\n");
244  break;
245  case BINDSTATUS_SENDINGREQUEST:
246  ODS("Sending request...\n");
247  break;
248  case BINDSTATUS_CLASSIDAVAILABLE:
249  ODS("CLSID available...\n");
250  break;
251  case BINDSTATUS_MIMETYPEAVAILABLE:
252  ODS("MIME type available...\n");
253  break;
254  case BINDSTATUS_CACHEFILENAMEAVAILABLE:
255  ODS("Cache file name available...\n");
256  if ( m_request->stream ) m_request->stream->signalCacheFilename( szStatusText );
257  break;
258  // also see OnResponse for these two
259  case BINDSTATUS_RAWMIMETYPE:
260  // never called?
261  //if ( m_request->stream ) m_request->stream->mimeType = FB::wstring_to_utf8( szStatusText );
262  break;
263  case BINDSTATUS_ACCEPTRANGES:
264  // never called?
265  //if ( m_request->stream ) m_request->stream->seekable = true;
266  break;
267  default:
268  ODS("Unknown binding status code!\n");
269  break;
270  }
271 
272  //wsprintf(szProgress, "%d of %d", ulProgress, (ulProgress>ulProgressMax)?ulProgress:ulProgressMax);
273 
274  //SetStatusText(szProgress, SBAR_PART_PROGRESS);
275  //UpdateProgress(ulProgress, ulProgressMax);
276 
277  return NOERROR;
278 } // ActiveXBindStatusCallback::OnProgress
279 
280 // ---------------------------------------------------------------------------
281 // %%Function: ActiveXBindStatusCallback::OnStopBinding
282 // ---------------------------------------------------------------------------
283  STDMETHODIMP
284 ActiveXBindStatusCallback::OnStopBinding(HRESULT hrStatus, LPCWSTR pszError)
285 {
286  if ( m_request->stream ) m_request->stream->signalRequestCompleted( m_request, !FAILED(hrStatus) );
287 
288  if (m_pbinding)
289  {
290  m_pbinding->Release();
291  m_pbinding = NULL;
292  }
293 
294  ODS("OnStopBinding\n");
295 
296  return S_OK;
297 } // ActiveXBindStatusCallback::OnStopBinding
298 
299 // ---------------------------------------------------------------------------
300 // %%Function: ActiveXBindStatusCallback::GetBindInfo
301 // ---------------------------------------------------------------------------
302  STDMETHODIMP
303 ActiveXBindStatusCallback::GetBindInfo(DWORD* pgrfBINDF, BINDINFO* pbindInfo)
304 {
305  *pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
306  if ( m_request->stream && m_request->stream->isCached() ) *pgrfBINDF |= BINDF_NEEDFILE;
307  else *pgrfBINDF |= BINDF_NOWRITECACHE | BINDF_PRAGMA_NO_CACHE | BINDF_GETNEWESTVERSION;
308 
309  // Set up the BINDINFO data structure
310  pbindInfo->cbSize = sizeof(BINDINFO);
311  pbindInfo->dwBindVerb = m_dwAction; // here's where the action is defined
312  pbindInfo->szExtraInfo = NULL;
313 
314  // Initialize the STGMEDIUM.
315  memset(&pbindInfo->stgmedData, 0, sizeof(STGMEDIUM));
316  pbindInfo->grfBindInfoF = 0;
317  pbindInfo->szCustomVerb = NULL;
318 
319  // set up action-specific members
320  switch(m_dwAction)
321  {
322  case BINDVERB_POST:
323  if (m_hDataToPost)
324  {
325  // Fill the STGMEDIUM with the data to post
326  pbindInfo->stgmedData.tymed = TYMED_HGLOBAL; // this is the only medium urlmon supports right now
327  pbindInfo->stgmedData.hGlobal = m_hDataToPost;
328  pbindInfo->stgmedData.pUnkForRelease = (LPUNKNOWN)(LPBINDSTATUSCALLBACK)this; // maintain control over the data.
329  AddRef(); // It will be freed on final release
330  pbindInfo->cbstgmedData = // this must be exact!
331  m_cbDataToPost; // Do not rely on GlobalSize()
332  // which rounds up to the nearest power of two.
333  }
334  break;
335  case BINDVERB_GET:
336  break;
337  default:
338  return E_FAIL;
339  }
340 
341  return S_OK;
342 } // ActiveXBindStatusCallback::GetBindInfo
343 
344 // ---------------------------------------------------------------------------
345 // %%Function: ActiveXBindStatusCallback::OnDataAvailable
346 // ---------------------------------------------------------------------------
347  STDMETHODIMP
348 ActiveXBindStatusCallback::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC* pfmtetc, STGMEDIUM* pstgmed)
349 {
350  HRESULT hr = S_OK;
351 
352  if (BSCF_FIRSTDATANOTIFICATION & grfBSCF)
353  {
354  // Cache the incoming Stream
355  if (!m_pstm && pstgmed->tymed == TYMED_ISTREAM)
356  {
357  m_pstm = pstgmed->pstm;
358  }
359  }
360 
361  // If there is some data to be read then go ahead and read it
362  if (m_pstm && dwSize > m_cbOld)
363  {
364  DWORD dwRead = dwSize - m_cbOld; // Minimum amount available that hasn't been read
365  DWORD dwActuallyRead = 0; // Placeholder for amount read during this pull
366 
367  DWORD offset = 0;
368  std::string data;
369  if ( GetInfo( HTTP_QUERY_CONTENT_RANGE, data ) ) // data look like bytes 0-3/4234
370  {
371  size_t endPos = data.find( "-" );
372  if ( endPos != std::string::npos )
373  {
374  size_t startPos = 6; // "bytes "
375  offset = atol( data.substr(startPos, endPos - startPos).c_str() );
376  }
377  }
378 
379  if (dwRead > 0)
380  do
381  {
382  boost::shared_array<char> cur(new char[dwRead]);
383 
384  hr = m_pstm->Read( cur.get(), dwRead, &dwActuallyRead);
385 
386  // If we really read something then lets add it to the Edit box
387  if (dwActuallyRead > 0)
388  {
389  if ( m_request->stream ) m_request->stream->signalDataArrived( cur.get(), dwActuallyRead, offset + m_cbOld );
390  m_cbOld += dwActuallyRead;
391  }
392 
393  } while (!(hr == E_PENDING || hr == S_FALSE) && SUCCEEDED(hr));
394  }
395 
396  // Clean up
397  if (BSCF_LASTDATANOTIFICATION & grfBSCF)
398  {
399  if (m_pstm)
400  {
401  m_pstm.Release();
402  }
403 
404  hr = S_OK; // If it was the last data then we should return S_OK as we just finished reading everything
405 
406  //SetStatusText(_T("File downloaded."));
407 
408  ODS("OnProgress: Last notification\n");
409  }
410 
411  return hr;
412 } // ActiveXBindStatusCallback::OnDataAvailable
413 
414 // ---------------------------------------------------------------------------
415 // %%Function: ActiveXBindStatusCallback::OnObjectAvailable
416 // ---------------------------------------------------------------------------
417  STDMETHODIMP
418 ActiveXBindStatusCallback::OnObjectAvailable(REFIID riid, IUnknown* punk)
419 {
420  return E_NOTIMPL;
421 } // ActiveXBindStatusCallback::OnObjectAvailable
422 
423 // ---------------------------------------------------------------------------
424 // %%Function: ActiveXBindStatusCallback::BeginningTransaction
425 // ---------------------------------------------------------------------------
426 STDMETHODIMP ActiveXBindStatusCallback::BeginningTransaction(LPCWSTR szURL,
427  LPCWSTR szHeaders,
428  DWORD dwReserved,
429  LPWSTR __RPC_FAR *pszAdditionalHeaders)
430 {
431  // Here's our opportunity to add headers
432  if (!pszAdditionalHeaders)
433  {
434  return E_POINTER;
435  }
436 
437  *pszAdditionalHeaders = NULL;
438 
439  std::wstringstream extraHeaders;
440 
441  extraHeaders << L"Accept-Encoding: identity\r\n";
442 
443  if ( m_request->ranges.size() )
444  {
445  extraHeaders << L"Range: bytes=";
446  for ( std::vector<BrowserStream::Range>::const_iterator it = m_request->ranges.begin(); it != m_request->ranges.end(); ++it )
447  {
448  extraHeaders << it->start << L"-" << (it->end - 1);
449  if ( (it+1) != m_request->ranges.end() ) extraHeaders << L",";
450  }
451  extraHeaders << L"\r\n";
452  }
453 
454  // This header is required when performing a POST operation
455  if (BINDVERB_POST == m_dwAction && m_hDataToPost)
456  {
457  extraHeaders << L"Content-Type: application/x-www-form-urlencoded\r\n";
458  }
459 
460  LPWSTR wszAdditionalHeaders =
461  (LPWSTR)CoTaskMemAlloc((extraHeaders.str().size()+1) *sizeof(WCHAR));
462  if (!wszAdditionalHeaders)
463  {
464  return E_OUTOFMEMORY;
465  }
466 
467  wcscpy(wszAdditionalHeaders, extraHeaders.str().c_str());
468  *pszAdditionalHeaders = wszAdditionalHeaders;
469 
470  m_transactionStarted = true;
471 
472  return NOERROR;
473 }
474 
475 // ---------------------------------------------------------------------------
476 // %%Function: ActiveXBindStatusCallback::OnResponse
477 // ---------------------------------------------------------------------------
478 STDMETHODIMP ActiveXBindStatusCallback::OnResponse(/* [in] */ DWORD dwResponseCode,
479  /* [unique][in] */ LPCWSTR szResponseHeaders,
480  /* [unique][in] */ LPCWSTR szRequestHeaders,
481  /* [out] */ LPWSTR __RPC_FAR *pszAdditionalRequestHeaders)
482 {
483  if (!pszAdditionalRequestHeaders)
484  {
485  return E_POINTER;
486  }
487 
488  *pszAdditionalRequestHeaders = NULL;
489 
490  if ( m_request->stream )
491  {
492  m_request->stream->setHeaders( wstring_to_utf8( szResponseHeaders ) );
493 
494  bool requestedSeekable = m_request->stream->isSeekableRequested();
495 
496  std::string data;
497  if ( GetInfo( HTTP_QUERY_CONTENT_LENGTH, data ) ) m_request->stream->setLength( atol( data.c_str() ) ); // nasty, should use a stringstream for conversion here
498  if ( GetInfo( HTTP_QUERY_CONTENT_TYPE, data ) ) m_request->stream->setMimeType( data );
499  if ( GetInfo( HTTP_QUERY_ACCEPT_RANGES, data ) ) m_request->stream->setSeekableByServer( data == "bytes" );
500 
501  bool ok = ( dwResponseCode >= 200 && dwResponseCode < 300 );
502  if ( requestedSeekable && !m_request->stream->isSeekableByServer() ) ok = false;
503 
504  if ( ok )
505  {
506  if ( !m_request->stream->isOpen() ) m_request->stream->signalOpened();
507  return NOERROR;
508  }
509  else
510  {
511  m_request->stream->signalFailedOpen();
512  return E_ABORT;
513  }
514  }
515 
516  return E_ABORT;
517 }
518 
519 
520 bool ActiveXBindStatusCallback::GetInfo(DWORD which, std::string& result)
521 {
522  bool ok = false;
523 
524  DWORD bufferSize = 2048;
525  boost::scoped_array<char> buffer(new char[bufferSize]);
526  DWORD flags(0);
527 
528  CComPtr<IWinInetHttpInfo> httpInfo;
529  if ( !FAILED(m_pbinding->QueryInterface( &httpInfo ) ) )
530  {
531  ok = ( S_OK == httpInfo->QueryInfo( which, &buffer[0], &bufferSize, &flags, 0 ) );
532  if( ok )
533  result = std::string( buffer.get(), bufferSize );
534  }
535 
536  return ok;
537 }
538 
539 bool ActiveXBindStatusCallback::close()
540 {
541  HRESULT hr = m_pbinding->Abort();
542  return (!FAILED( hr )) || ( hr == S_FALSE ) || ( hr == INET_E_RESULT_DISPATCHED );
543 }
544 
545 
546 ActiveXStreamRequest::ActiveXStreamRequest( ActiveXStreamPtr Stream ) : stream(Stream)
547 {
548 }
549 
550 ActiveXStreamRequest::ActiveXStreamRequest( ActiveXStreamPtr Stream, const std::vector<BrowserStream::Range>& Ranges ) : stream(Stream), ranges(Ranges)
551 {
552 }
553 
554 bool ActiveXStreamRequest::start()
555 {
556  std::string url = stream->getUrl();
557  std::wstring wideUrl( url.begin(), url.end() );
558 
559  if ( FAILED( ActiveXBindStatusCallback::Create( &bindStatusCallback, shared_from_this() )) ) return false;
560  if ( FAILED( CreateURLMonikerEx(0, wideUrl.c_str(), &FMoniker, URL_MK_UNIFORM) ) ) return false;
561  if ( FAILED( CreateAsyncBindCtx(0, bindStatusCallback, 0, &FBindCtx) ) ) return false;
562  if ( FAILED( IsValidURL(FBindCtx, wideUrl.c_str(), 0) ) ) return false;
563  HRESULT hr = FMoniker->BindToStorage(FBindCtx, 0, IID_IStream, (void**)&fstream);
564 
565  if ( FAILED(hr) && (hr != MK_S_ASYNCHRONOUS) ) return false;
566 
567  return true;
568 }
569 
570 bool ActiveXStreamRequest::stop()
571 {
572  if ( !bindStatusCallback ) return true;
573  bool retVal = bindStatusCallback->close();
574  bindStatusCallback.Release();
575  stream.reset();
576  return retVal;
577 }
578 
std::string wstring_to_utf8(const std::wstring &src)
Accepts a std::wstring and returns a UTF8-encoded std::string.
Definition: utf8_tools.cpp:37