Team Fortress 2 Source Code as on 22/4/2020
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.

299 lines
7.6 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #if defined( WIN32 )
  5. #include "winlite.h"
  6. #include <WinInet.h>
  7. #endif
  8. #include "cl_downloader.h"
  9. #include "engine/requestcontext.h"
  10. #include "replaysystem.h"
  11. // memdbgon must be the last include file in a .cpp file!!!
  12. #include "tier0/memdbgon.h"
  13. //----------------------------------------------------------------------------------------
  14. extern IEngineClientReplay *g_pEngineClient;
  15. extern IEngineReplay *g_pEngine;
  16. //----------------------------------------------------------------------------------------
  17. CHttpDownloader::CHttpDownloader( IDownloadHandler *pHandler )
  18. : m_pHandler( pHandler ),
  19. m_flNextThinkTime( 0.0f ),
  20. m_uBytesDownloaded( 0 ),
  21. m_uSize( 0 ),
  22. m_pThreadState( NULL ),
  23. m_bDone( false ),
  24. m_nHttpError( HTTP_ERROR_NONE ),
  25. m_nHttpStatus( HTTP_INVALID ),
  26. m_pBytesDownloaded( NULL ),
  27. m_pUserData( NULL )
  28. {
  29. }
  30. CHttpDownloader::~CHttpDownloader()
  31. {
  32. CleanupThreadIfDone();
  33. }
  34. bool CHttpDownloader::CleanupThreadIfDone()
  35. {
  36. if ( !m_pThreadState || !m_pThreadState->threadDone )
  37. return false;
  38. // NOTE: The context's "data" member will have already been cleaned up by the
  39. // download thread at this point.
  40. delete m_pThreadState;
  41. m_pThreadState = NULL;
  42. return true;
  43. }
  44. bool CHttpDownloader::BeginDownload( const char *pURL, const char *pGamePath, void *pUserData, uint32 *pBytesDownloaded )
  45. {
  46. if ( !pURL || !pURL[0] )
  47. return false;
  48. m_pThreadState = new RequestContext_t();
  49. if ( !m_pThreadState )
  50. return false;
  51. // Cache any user data
  52. m_pUserData = pUserData;
  53. // Cache bytes downloaded
  54. m_pBytesDownloaded = pBytesDownloaded;
  55. // Setup request context
  56. Replay_CrackURL( pURL, m_pThreadState->baseURL, m_pThreadState->urlPath );
  57. m_pThreadState->bAsHTTP = true;
  58. if ( pGamePath )
  59. {
  60. m_pThreadState->bSuppressFileWrite = false;
  61. V_strcpy( m_pThreadState->gamePath, pGamePath );
  62. // Generate the actual filename to save. Well, it's not
  63. // absolute, but this will work.
  64. V_strcpy_safe( m_pThreadState->absLocalPath, g_pEngine->GetGameDir() );
  65. V_AppendSlash( m_pThreadState->absLocalPath, sizeof(m_pThreadState->absLocalPath) );
  66. V_strcat_safe( m_pThreadState->absLocalPath, pGamePath );
  67. }
  68. else
  69. {
  70. m_pThreadState->bSuppressFileWrite = true;
  71. }
  72. // Cache URL - for debugging
  73. V_strcpy( m_szURL, pURL );
  74. // Spawn the download thread
  75. extern IDownloadSystem *g_pDownloadSystem;
  76. return g_pDownloadSystem->CreateDownloadThread( m_pThreadState ) != 0;
  77. }
  78. void CHttpDownloader::AbortDownloadAndCleanup()
  79. {
  80. // Make sure that this function isn't executed simultaneously by
  81. // multiple threads in order to avoid use-after-free crashes during
  82. // shutdown.
  83. AUTO_LOCK( m_lock );
  84. if ( !m_pThreadState )
  85. return;
  86. // Thread already completed?
  87. if ( m_pThreadState->threadDone )
  88. {
  89. CleanupThreadIfDone();
  90. return;
  91. }
  92. // Loop until the thread cleans up
  93. m_pThreadState->shouldStop = true;
  94. while ( !m_pThreadState->threadDone )
  95. ;
  96. // Cache state for handler
  97. m_nHttpError = m_pThreadState->error;
  98. m_nHttpStatus = HTTP_ABORTED; // Force this to be safe
  99. m_uBytesDownloaded = 0;
  100. m_uSize = m_pThreadState->nBytesTotal;
  101. m_bDone = true;
  102. InvokeHandler();
  103. CleanupThreadIfDone();
  104. }
  105. void CHttpDownloader::Think()
  106. {
  107. const float flHostTime = g_pEngine->GetHostTime();
  108. if ( m_flNextThinkTime > flHostTime )
  109. return;
  110. if ( !m_pThreadState )
  111. return;
  112. // If thread is done, cleanup now
  113. if ( CleanupThreadIfDone() )
  114. return;
  115. // If we haven't already set shouldStop, check the download status
  116. if ( !m_pThreadState->shouldStop )
  117. {
  118. // Security measure: make sure the file size isn't outrageous
  119. const bool bEvilFileSize = m_pThreadState->nBytesTotal &&
  120. m_pThreadState->nBytesTotal >= DOWNLOAD_MAX_SIZE;
  121. #if _DEBUG
  122. extern ConVar replay_simulate_evil_download_size;
  123. if ( replay_simulate_evil_download_size.GetBool() || bEvilFileSize )
  124. #else
  125. if ( bEvilFileSize )
  126. #endif
  127. {
  128. AbortDownloadAndCleanup();
  129. return;
  130. }
  131. bool bConnecting = false; // For fall-through in HTTP_CONNECTING case.
  132. #if _DEBUG
  133. extern ConVar replay_simulatedownloadfailure;
  134. if ( replay_simulatedownloadfailure.GetInt() == 1 )
  135. {
  136. m_pThreadState->status = HTTP_ERROR;
  137. }
  138. #endif
  139. switch ( m_pThreadState->status )
  140. {
  141. case HTTP_CONNECTING:
  142. // Call connecting handler
  143. if ( m_pHandler )
  144. {
  145. m_pHandler->OnConnecting( this );
  146. }
  147. bConnecting = true;
  148. // Fall-through
  149. case HTTP_FETCH:
  150. m_uBytesDownloaded = (uint32)m_pThreadState->nBytesCurrent;
  151. m_uSize = m_pThreadState->nBytesTotal;
  152. Assert( m_uBytesDownloaded <= m_uSize );
  153. // Call fetch handle
  154. if ( !bConnecting && m_pHandler )
  155. {
  156. m_pHandler->OnFetch( this );
  157. }
  158. break;
  159. case HTTP_ABORTED:
  160. case HTTP_DONE:
  161. case HTTP_ERROR:
  162. // Cache state
  163. m_nHttpError = m_pThreadState->error;
  164. m_nHttpStatus = m_pThreadState->status;
  165. m_uBytesDownloaded = (uint32)m_pThreadState->nBytesCurrent;
  166. m_uSize = m_pThreadState->nBytesTotal; // NOTE: Need to do this here in the case that a file is small enough that we never hit HTTP_FETCH
  167. m_bDone = true;
  168. // Call handler
  169. InvokeHandler();
  170. // Tell the thread to cleanup so we can free it
  171. m_pThreadState->shouldStop = true;
  172. break;
  173. }
  174. }
  175. // Write bytes for user if changed
  176. if ( m_pBytesDownloaded && *m_pBytesDownloaded != m_uBytesDownloaded )
  177. {
  178. *m_pBytesDownloaded = m_uBytesDownloaded;
  179. IF_REPLAY_DBG( Warning( "%s: Downloaded %i/%i bytes\n", m_szURL, m_uBytesDownloaded, m_uSize ) );
  180. }
  181. // Set next think time
  182. m_flNextThinkTime = flHostTime + 0.1f;
  183. }
  184. void CHttpDownloader::InvokeHandler()
  185. {
  186. if ( !m_pHandler )
  187. return;
  188. // NOTE: Don't delete the downloader in OnDownloadComplete()!
  189. m_pHandler->OnDownloadComplete( this, m_pThreadState->data );
  190. }
  191. // This does not increment the "ErrorCounter" field and should only be called from code
  192. // that eventually calls into OGS_ReportGenericError().
  193. KeyValues *CHttpDownloader::GetOgsRow( int nErrorCounter ) const
  194. {
  195. KeyValues *pResult = new KeyValues( "TF2ReplayHttpDownloadErrors" );
  196. pResult->SetInt( "ErrorCounter", nErrorCounter );
  197. pResult->SetInt( "BytesDownloaded", (int)m_uBytesDownloaded );
  198. pResult->SetInt( "BytesTotal", (int)m_uSize );
  199. pResult->SetInt( "HttpStatus", m_nHttpStatus );
  200. pResult->SetInt( "HttpError", m_nHttpError );
  201. pResult->SetString( "URL", m_szURL );
  202. return pResult;
  203. }
  204. /*static*/ const char *CHttpDownloader::GetHttpErrorToken( HTTPError_t nError )
  205. {
  206. switch ( nError )
  207. {
  208. case HTTP_ERROR_ZERO_LENGTH_FILE: return "#HTTPError_ZeroLengthFile";
  209. case HTTP_ERROR_CONNECTION_CLOSED: return "#HTTPError_ConnectionClosed";
  210. case HTTP_ERROR_INVALID_URL: return "#HTTPError_InvalidURL";
  211. case HTTP_ERROR_INVALID_PROTOCOL: return "#HTTPError_InvalidProtocol";
  212. case HTTP_ERROR_CANT_BIND_SOCKET: return "#HTTPError_CantBindSocket";
  213. case HTTP_ERROR_CANT_CONNECT: return "#HTTPError_CantConnect";
  214. case HTTP_ERROR_NO_HEADERS: return "#HTTPError_NoHeaders";
  215. case HTTP_ERROR_FILE_NONEXISTENT: return "#HTTPError_NonExistent";
  216. }
  217. return "#HTTPError_Unknown";
  218. }
  219. //----------------------------------------------------------------------------------------
  220. #ifdef _DEBUG
  221. CHttpDownloader *g_pTestDownload = NULL;
  222. ConVar replay_forcedownloadurl( "replay_forcedownloadurl", "" );
  223. CON_COMMAND( replay_testdownloader_start, "" )
  224. {
  225. const char *pGamePath = Replay_va( "%s%s", CL_GetRecordingSessionBlockManager()->GetSavePath(), "testdownload" );
  226. g_pTestDownload = new CHttpDownloader();
  227. g_pTestDownload->BeginDownload( args[1], pGamePath );
  228. }
  229. CON_COMMAND( replay_testdownloader_abort, "" )
  230. {
  231. if ( !g_pTestDownload )
  232. return;
  233. g_pTestDownload->AbortDownloadAndCleanup();
  234. delete g_pTestDownload;
  235. g_pTestDownload = NULL;
  236. }
  237. #endif