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.

940 lines
28 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. //--------------------------------------------------------------------------------------------------------------
  9. // downloadthread.cpp
  10. //
  11. // Implementation file for optional HTTP asset downloading thread
  12. // Author: Matthew D. Campbell ([email protected]), 2004
  13. //--------------------------------------------------------------------------------------------------------------
  14. //--------------------------------------------------------------------------------------------------------------
  15. // Includes
  16. //--------------------------------------------------------------------------------------------------------------
  17. #if defined( WIN32 ) && !defined( _X360 )
  18. #include "winlite.h"
  19. #include <WinInet.h>
  20. #endif
  21. #include <assert.h>
  22. #include <sys/stat.h>
  23. #include <stdio.h>
  24. #include "tier0/platform.h"
  25. #include "tier0/dbg.h"
  26. #include "download_internal.h"
  27. #include "tier1/strtools.h"
  28. #include "tier0/threadtools.h"
  29. #if defined( _X360 )
  30. #include "xbox/xbox_win32stubs.h"
  31. #endif
  32. // memdbgon must be the last include file in a .cpp file!!!
  33. #include "tier0/memdbgon.h"
  34. //--------------------------------------------------------------------------------------------------------------
  35. void WriteFileFromRequestContext( const RequestContext_t &rc )
  36. {
  37. struct stat buf;
  38. int rt = stat(rc.absLocalPath, &buf);
  39. if ( rt == -1 && !rc.bSuppressFileWrite )
  40. {
  41. FILE *fp = fopen( rc.absLocalPath, "wb" );
  42. if ( fp )
  43. {
  44. if ( rc.data )
  45. {
  46. fwrite( rc.data, rc.nBytesTotal, 1, fp );
  47. }
  48. fclose( fp );
  49. }
  50. }
  51. }
  52. //--------------------------------------------------------------------------------------------------------------
  53. #ifdef _WIN32
  54. //--------------------------------------------------------------------------------------------------------------
  55. /**
  56. * Formats a string to spit out via OutputDebugString (only in debug). OutputDebugString
  57. * is threadsafe, so this should be fine.
  58. *
  59. * Since I don't want to be playing with the developer cvar in the other thread, I'll use
  60. * the presence of _DEBUG as my developer flag.
  61. */
  62. void Thread_DPrintf (char *fmt, ...)
  63. {
  64. #ifdef _DEBUG
  65. va_list argptr;
  66. char msg[4096];
  67. va_start( argptr, fmt );
  68. Q_vsnprintf( msg, sizeof(msg), fmt, argptr );
  69. va_end( argptr );
  70. OutputDebugString( msg );
  71. #endif // _DEBUG
  72. }
  73. //--------------------------------------------------------------------------------------------------------------
  74. /**
  75. * Convenience function to name status states for debugging
  76. */
  77. static const char *StateString( DWORD dwStatus )
  78. {
  79. switch (dwStatus)
  80. {
  81. case INTERNET_STATUS_RESOLVING_NAME:
  82. return "INTERNET_STATUS_RESOLVING_NAME";
  83. case INTERNET_STATUS_NAME_RESOLVED:
  84. return "INTERNET_STATUS_NAME_RESOLVED";
  85. case INTERNET_STATUS_CONNECTING_TO_SERVER:
  86. return "INTERNET_STATUS_CONNECTING_TO_SERVER";
  87. case INTERNET_STATUS_CONNECTED_TO_SERVER:
  88. return "INTERNET_STATUS_CONNECTED_TO_SERVER";
  89. case INTERNET_STATUS_SENDING_REQUEST:
  90. return "INTERNET_STATUS_SENDING_REQUEST";
  91. case INTERNET_STATUS_REQUEST_SENT:
  92. return "INTERNET_STATUS_REQUEST_SENT";
  93. case INTERNET_STATUS_REQUEST_COMPLETE:
  94. return "INTERNET_STATUS_REQUEST_COMPLETE";
  95. case INTERNET_STATUS_CLOSING_CONNECTION:
  96. return "INTERNET_STATUS_CLOSING_CONNECTION";
  97. case INTERNET_STATUS_CONNECTION_CLOSED:
  98. return "INTERNET_STATUS_CONNECTION_CLOSED";
  99. case INTERNET_STATUS_RECEIVING_RESPONSE:
  100. return "INTERNET_STATUS_RECEIVING_RESPONSE";
  101. case INTERNET_STATUS_RESPONSE_RECEIVED:
  102. return "INTERNET_STATUS_RESPONSE_RECEIVED";
  103. case INTERNET_STATUS_HANDLE_CLOSING:
  104. return "INTERNET_STATUS_HANDLE_CLOSING";
  105. case INTERNET_STATUS_HANDLE_CREATED:
  106. return "INTERNET_STATUS_HANDLE_CREATED";
  107. case INTERNET_STATUS_INTERMEDIATE_RESPONSE:
  108. return "INTERNET_STATUS_INTERMEDIATE_RESPONSE";
  109. case INTERNET_STATUS_REDIRECT:
  110. return "INTERNET_STATUS_REDIRECT";
  111. case INTERNET_STATUS_STATE_CHANGE:
  112. return "INTERNET_STATUS_STATE_CHANGE";
  113. }
  114. return "Unknown";
  115. }
  116. //--------------------------------------------------------------------------------------------------------------
  117. /**
  118. * Callback function to update status information for a download (connecting to server, etc)
  119. */
  120. void __stdcall DownloadStatusCallback( HINTERNET hOpenResource, DWORD dwContext, DWORD dwStatus, LPVOID pStatusInfo, DWORD dwStatusInfoLength )
  121. {
  122. RequestContext_t *rc = (RequestContext_t*)pStatusInfo;
  123. switch (dwStatus)
  124. {
  125. case INTERNET_STATUS_RESOLVING_NAME:
  126. case INTERNET_STATUS_NAME_RESOLVED:
  127. case INTERNET_STATUS_CONNECTING_TO_SERVER:
  128. case INTERNET_STATUS_CONNECTED_TO_SERVER:
  129. case INTERNET_STATUS_SENDING_REQUEST:
  130. case INTERNET_STATUS_REQUEST_SENT:
  131. case INTERNET_STATUS_REQUEST_COMPLETE:
  132. case INTERNET_STATUS_CLOSING_CONNECTION:
  133. case INTERNET_STATUS_CONNECTION_CLOSED:
  134. if ( rc )
  135. {
  136. rc->fetchStatus = dwStatus;
  137. }
  138. else
  139. {
  140. //Thread_DPrintf( "** No RequestContext_t **\n" );
  141. }
  142. //Thread_DPrintf( "DownloadStatusCallback %s\n", StateString(dwStatus) );
  143. break;
  144. }
  145. }
  146. //--------------------------------------------------------------------------------------------------------------
  147. /**
  148. * Reads data from a handle opened by InternetOpenUrl().
  149. */
  150. void ReadData( RequestContext_t& rc )
  151. {
  152. const int BufferSize = 2048;
  153. unsigned char data[BufferSize];
  154. DWORD dwSize = 0;
  155. if ( !rc.nBytesTotal )
  156. {
  157. rc.status = HTTP_ERROR;
  158. rc.error = HTTP_ERROR_ZERO_LENGTH_FILE;
  159. return;
  160. }
  161. rc.nBytesCurrent = rc.nBytesCached;
  162. rc.status = HTTP_FETCH;
  163. while ( !rc.shouldStop )
  164. {
  165. // InternetReadFile() will block until there is data, or the socket gets closed. This means the
  166. // main thread could request an abort while we're blocked here. This is okay, because the main
  167. // thread will not wait for this thread to finish, but will clean up the RequestContext_t at some
  168. // later point when InternetReadFile() has returned and this thread has finished.
  169. if ( !InternetReadFile( rc.hDataResource, (LPVOID)data, BufferSize, &dwSize ) )
  170. {
  171. // if InternetReadFile() returns 0, there was a socket error (connection closed, etc)
  172. rc.status = HTTP_ERROR;
  173. rc.error = HTTP_ERROR_CONNECTION_CLOSED;
  174. return;
  175. }
  176. if ( !dwSize )
  177. {
  178. // if InternetReadFile() succeeded, but we read 0 bytes, we're at the end of the file.
  179. // if the file doesn't exist, write it out
  180. WriteFileFromRequestContext( rc );
  181. // Let the main thread know we finished reading data, and wait for it to let us exit.
  182. rc.status = HTTP_DONE;
  183. return;
  184. }
  185. else
  186. {
  187. // We've read some data. Make sure we won't walk off the end of our buffer, then
  188. // use memcpy() to save the data off.
  189. DWORD safeSize = (DWORD) min( rc.nBytesTotal - rc.nBytesCurrent, (int)dwSize );
  190. //Thread_DPrintf( "Read %d bytes @ offset %d\n", safeSize, rc.nBytesCurrent );
  191. if ( safeSize != dwSize )
  192. {
  193. //Thread_DPrintf( "Warning - read more data than expected!\n" );
  194. }
  195. if ( rc.data && safeSize > 0 )
  196. {
  197. memcpy( rc.data + rc.nBytesCurrent, data, safeSize );
  198. }
  199. rc.nBytesCurrent += safeSize;
  200. }
  201. }
  202. // If we get here, rc.shouldStop was set early (user hit cancel).
  203. // Let the main thread know we aborted properly.
  204. rc.status = HTTP_ABORTED;
  205. }
  206. //--------------------------------------------------------------------------------------------------------------
  207. const char *StatusString[] =
  208. {
  209. "HTTP_CONNECTING",
  210. "HTTP_FETCH",
  211. "HTTP_DONE",
  212. "HTTP_ABORTED",
  213. "HTTP_ERROR",
  214. };
  215. //--------------------------------------------------------------------------------------------------------------
  216. const char *ErrorString[] =
  217. {
  218. "HTTP_ERROR_NONE",
  219. "HTTP_ERROR_ZERO_LENGTH_FILE",
  220. "HTTP_ERROR_CONNECTION_CLOSED",
  221. "HTTP_ERROR_INVALID_URL",
  222. "HTTP_ERROR_INVALID_PROTOCOL",
  223. "HTTP_ERROR_CANT_BIND_SOCKET",
  224. "HTTP_ERROR_CANT_CONNECT",
  225. "HTTP_ERROR_NO_HEADERS",
  226. "HTTP_ERROR_FILE_NONEXISTENT",
  227. "HTTP_ERROR_MAX",
  228. };
  229. //--------------------------------------------------------------------------------------------------------------
  230. /**
  231. * Closes all open handles, and waits until the main thread has given the OK
  232. * to quit.
  233. */
  234. void CleanUpDownload( RequestContext_t& rc, HTTPStatus_t status, HTTPError_t error = HTTP_ERROR_NONE )
  235. {
  236. if ( status != HTTP_DONE || error != HTTP_ERROR_NONE )
  237. {
  238. //Thread_DPrintf( "CleanUpDownload() - http status is %s, error state is %s\n",
  239. // StatusString[status], ErrorString[error] );
  240. }
  241. rc.status = status;
  242. rc.error = error;
  243. // Close all HINTERNET handles we have open
  244. if ( rc.hDataResource && !InternetCloseHandle(rc.hDataResource) )
  245. {
  246. //Thread_DPrintf( "Failed to close data resource for %s%s\n", rc.baseURL, rc.gamePath );
  247. }
  248. else if ( rc.hOpenResource && !InternetCloseHandle(rc.hOpenResource) )
  249. {
  250. //Thread_DPrintf( "Failed to close open resource for %s%s\n", rc.baseURL, rc.gamePath );
  251. }
  252. rc.hDataResource = NULL;
  253. rc.hOpenResource = NULL;
  254. // wait until the main thread says we can go away (so it can look at rc.data).
  255. while ( !rc.shouldStop )
  256. {
  257. Sleep( 100 );
  258. }
  259. // Delete rc.data, which was allocated in this thread
  260. if ( rc.data != NULL )
  261. {
  262. delete[] rc.data;
  263. rc.data = NULL;
  264. }
  265. // and tell the main thread we're exiting, so it can delete rc.cachedData and rc itself.
  266. rc.threadDone = true;
  267. }
  268. //--------------------------------------------------------------------------------------------------------------
  269. static void DumpHeaders( RequestContext_t& rc )
  270. {
  271. #ifdef _DEBUG
  272. DWORD dwSize;
  273. // First time we will find out the size of the headers.
  274. HttpQueryInfo ( rc.hDataResource, HTTP_QUERY_RAW_HEADERS_CRLF, NULL, &dwSize, NULL );
  275. char *lpBuffer = new char [dwSize + 2];
  276. // Now we call HttpQueryInfo again to get the headers.
  277. if (!HttpQueryInfo ( rc.hDataResource, HTTP_QUERY_RAW_HEADERS_CRLF, (LPVOID)lpBuffer, &dwSize, NULL))
  278. {
  279. return;
  280. }
  281. *(lpBuffer + dwSize) = '\n';
  282. *(lpBuffer + dwSize + 1) = '\0';
  283. Thread_DPrintf( "------------------------------\n%s%s\n%s------------------------------\n",
  284. rc.baseURL, rc.gamePath, lpBuffer );
  285. #endif
  286. }
  287. //--------------------------------------------------------------------------------------------------------------
  288. /**
  289. * Main download thread function - implements a (partial) synchronous HTTP download.
  290. */
  291. DWORD __stdcall DownloadThread( void *voidPtr )
  292. {
  293. RequestContext_t& rc = *(RequestContext_t *)voidPtr;
  294. URL_COMPONENTS url;
  295. char urlBuf[6][BufferSize];
  296. url.dwStructSize = sizeof(url);
  297. url.dwSchemeLength = BufferSize;
  298. url.dwHostNameLength = BufferSize;
  299. url.dwUserNameLength = BufferSize;
  300. url.dwPasswordLength = BufferSize;
  301. url.dwUrlPathLength = BufferSize;
  302. url.dwExtraInfoLength = BufferSize;
  303. url.lpszScheme = urlBuf[0];
  304. url.lpszHostName = urlBuf[1];
  305. url.lpszUserName = urlBuf[2];
  306. url.lpszPassword = urlBuf[3];
  307. url.lpszUrlPath = urlBuf[4];
  308. url.lpszExtraInfo = urlBuf[5];
  309. char fullURL[BufferSize*2];
  310. DWORD fullURLLength = BufferSize*2;
  311. Q_snprintf( fullURL, fullURLLength, "%s%s", rc.baseURL, rc.urlPath );
  312. if ( !InternetCrackUrl( fullURL, fullURLLength, 0, &url ) )
  313. {
  314. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_INVALID_URL );
  315. return rc.status;
  316. }
  317. /// @TODO: Add FTP support (including restart of aborted transfers) -MDC 2004/01/08
  318. // We should be able to handle FTP downloads as well, but I don't have a server to check against, so
  319. // I'm gonna disallow it in case something bad would happen.
  320. if ( url.nScheme != INTERNET_SCHEME_HTTP && url.nScheme != INTERNET_SCHEME_HTTPS )
  321. {
  322. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_INVALID_PROTOCOL );
  323. return rc.status;
  324. }
  325. // Open a socket etc for the download.
  326. // The first parameter, "Half-Life", is the User-Agent that gets sent with HTTP requests.
  327. // INTERNET_OPEN_TYPE_PRECONFIG specifies using IE's proxy info from the registry for HTTP downloads.
  328. rc.hOpenResource = InternetOpen( "Half-Life 2", INTERNET_OPEN_TYPE_PRECONFIG ,NULL, NULL, 0);
  329. if ( !rc.hOpenResource )
  330. {
  331. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_CANT_BIND_SOCKET );
  332. return rc.status;
  333. }
  334. InternetSetStatusCallback( rc.hOpenResource, (INTERNET_STATUS_CALLBACK)DownloadStatusCallback );
  335. if ( rc.shouldStop )
  336. {
  337. CleanUpDownload( rc, HTTP_ABORTED );
  338. return rc.status;
  339. }
  340. // Set up some flags
  341. DWORD flags = 0;
  342. flags |= INTERNET_FLAG_RELOAD; // Get from server, not IE's cache
  343. flags |= INTERNET_FLAG_NO_CACHE_WRITE; // Don't write to IE's cache, since we're doing our own caching of partial downloads
  344. flags |= INTERNET_FLAG_KEEP_CONNECTION; // Use keep-alive semantics. Since each file downloaded is a separate connection
  345. // from a separate thread, I don't think this does much. Can't hurt, though.
  346. if ( url.nScheme == INTERNET_SCHEME_HTTPS )
  347. {
  348. // The following flags allow us to use https:// URLs, but don't provide much in the way of authentication.
  349. // In other words, this allows people with only access to https:// servers (?!?) to host files as transparently
  350. // as possible.
  351. flags |= INTERNET_FLAG_SECURE; // Use SSL, etc. Kinda need this for HTTPS URLs.
  352. flags |= INTERNET_FLAG_IGNORE_CERT_CN_INVALID; // Don't check hostname on the SSL cert.
  353. flags |= INTERNET_FLAG_IGNORE_CERT_DATE_INVALID; // Don't check for expired SSL certs.
  354. }
  355. // Request a partial if we have the data
  356. char headers[BufferSize] = "";
  357. DWORD headerLen = 0;
  358. char *headerPtr = NULL;
  359. if ( *rc.cachedTimestamp && rc.nBytesCached )
  360. {
  361. if ( *rc.serverURL )
  362. {
  363. Q_snprintf( headers, BufferSize, "If-Range: %s\nRange: bytes=%d-\nReferer: hl2://%s\n",
  364. rc.cachedTimestamp, rc.nBytesCached, rc.serverURL );
  365. }
  366. else
  367. {
  368. Q_snprintf( headers, BufferSize, "If-Range: %s\nRange: bytes=%d-\n",
  369. rc.cachedTimestamp, rc.nBytesCached );
  370. }
  371. headerPtr = headers;
  372. headerLen = (DWORD)-1L; // the DWORD cast is because we get a signed/unsigned mismatch even with an L on the -1.
  373. //Thread_DPrintf( "Requesting partial download\n%s", headers );
  374. }
  375. else if ( *rc.serverURL )
  376. {
  377. Q_snprintf( headers, BufferSize, "Referer: hl2://%s\n", rc.serverURL );
  378. headerPtr = headers;
  379. headerLen = (DWORD)-1L; // the DWORD cast is because we get a signed/unsigned mismatch even with an L on the -1.
  380. //Thread_DPrintf( "Requesting full download\n%s", headers );
  381. }
  382. rc.hDataResource = InternetOpenUrl(rc.hOpenResource, fullURL, headerPtr, headerLen, flags,(DWORD)(&rc) );
  383. // send the request off
  384. if ( !rc.hDataResource )
  385. {
  386. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_CANT_CONNECT );
  387. return rc.status;
  388. }
  389. if ( rc.shouldStop )
  390. {
  391. CleanUpDownload( rc, HTTP_ABORTED );
  392. return rc.status;
  393. }
  394. //DumpHeaders( rc ); // debug
  395. // check the status (are we gonna get anything?)
  396. DWORD size = sizeof(DWORD);
  397. DWORD code;
  398. if ( !HttpQueryInfo( rc.hDataResource, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &code, &size, NULL ) )
  399. {
  400. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_NO_HEADERS );
  401. return rc.status;
  402. }
  403. // Only status codes we're looking for are HTTP_STATUS_OK (200) and HTTP_STATUS_PARTIAL_CONTENT (206)
  404. if ( code != HTTP_STATUS_OK && code != HTTP_STATUS_PARTIAL_CONTENT )
  405. {
  406. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_FILE_NONEXISTENT );
  407. return rc.status;
  408. }
  409. // get the timestamp, and save it off for future resumes, in case we abort this transfer later.
  410. size = BufferSize;
  411. if ( !HttpQueryInfo( rc.hDataResource, HTTP_QUERY_LAST_MODIFIED, rc.cachedTimestamp, &size, NULL ) )
  412. {
  413. rc.cachedTimestamp[0] = 0;
  414. }
  415. rc.cachedTimestamp[BufferSize-1] = 0;
  416. // If we're not getting a partial download, don't use any cached data, even if we have some.
  417. if ( code != HTTP_STATUS_PARTIAL_CONTENT )
  418. {
  419. if ( rc.nBytesCached )
  420. {
  421. //Thread_DPrintf( "Partial download refused - getting full version\n" );
  422. }
  423. rc.nBytesCached = 0; // start from the beginning
  424. }
  425. // Check the resource size, and allocate a buffer
  426. size = sizeof(code);
  427. if ( HttpQueryInfo( rc.hDataResource, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &code, &size, NULL ) )
  428. {
  429. rc.nBytesTotal = code + rc.nBytesCached;
  430. if ( code > 0 )
  431. {
  432. rc.data = new unsigned char[rc.nBytesTotal + 1]; // Extra byte for NULL terminator
  433. if ( !rc.data )
  434. {
  435. // We're seeing crazy large numbers being returned in code (0x48e1e22), the new fails, and we crash.
  436. // This should probably be a different error?
  437. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_ZERO_LENGTH_FILE );
  438. return rc.status;
  439. }
  440. rc.data[ rc.nBytesTotal ] = 0;
  441. }
  442. }
  443. else
  444. {
  445. CleanUpDownload( rc, HTTP_ERROR, HTTP_ERROR_ZERO_LENGTH_FILE );
  446. return rc.status;
  447. }
  448. // copy cached data into buffer
  449. if ( rc.cacheData && rc.nBytesCached )
  450. {
  451. int len = min( rc.nBytesCached, rc.nBytesTotal );
  452. memcpy( rc.data, rc.cacheData, len );
  453. }
  454. if ( rc.shouldStop )
  455. {
  456. CleanUpDownload( rc, HTTP_ABORTED );
  457. return rc.status;
  458. }
  459. // now download the actual data
  460. ReadData( rc );
  461. // and stick around until the main thread has gotten the data.
  462. CleanUpDownload( rc, rc.status, rc.error );
  463. return rc.status;
  464. }
  465. #elif defined( POSIX )
  466. #include "curl/curl.h"
  467. // curl callback functions
  468. static size_t curlWriteFn( void *ptr, size_t size, size_t nmemb, void *stream)
  469. {
  470. RequestContext_t *pRC = (RequestContext_t *) stream;
  471. if ( pRC->nBytesTotal && pRC->nBytesCurrent + ( size * nmemb ) <= pRC->nBytesTotal )
  472. {
  473. Q_memcpy( pRC->data + pRC->nBytesCurrent, ptr, ( size * nmemb ) );
  474. pRC->nBytesCurrent += size * nmemb;
  475. }
  476. return size * nmemb;
  477. }
  478. int Q_StrTrim( char *pStr )
  479. {
  480. char *pSource = pStr;
  481. char *pDest = pStr;
  482. // skip white space at the beginning
  483. while ( *pSource != 0 && isspace( *pSource ) )
  484. {
  485. pSource++;
  486. }
  487. // copy everything else
  488. char *pLastWhiteBlock = NULL;
  489. char *pStart = pDest;
  490. while ( *pSource != 0 )
  491. {
  492. *pDest = *pSource++;
  493. if ( isspace( *pDest ) )
  494. {
  495. if ( pLastWhiteBlock == NULL )
  496. pLastWhiteBlock = pDest;
  497. }
  498. else
  499. {
  500. pLastWhiteBlock = NULL;
  501. }
  502. pDest++;
  503. }
  504. *pDest = 0;
  505. // did we end in a whitespace block?
  506. if ( pLastWhiteBlock != NULL )
  507. {
  508. // yep; shorten the string
  509. pDest = pLastWhiteBlock;
  510. *pLastWhiteBlock = 0;
  511. }
  512. return pDest - pStart;
  513. }
  514. static size_t curlHeaderFn( void *ptr, size_t size, size_t nmemb, void *stream)
  515. {
  516. char *pszHeader = (char*)ptr;
  517. char *pszValue = NULL;
  518. RequestContext_t *pRC = (RequestContext_t *) stream;
  519. pszHeader[ ( size * nmemb - 1 ) ] = NULL;
  520. pszValue = Q_strstr( pszHeader, ":" );
  521. if ( pszValue )
  522. {
  523. // null terminate the header name, and point pszValue at it's value
  524. *pszValue = NULL;
  525. pszValue++;
  526. Q_StrTrim( pszValue );
  527. }
  528. if ( 0 == Q_stricmp( pszHeader, "Content-Length" ) )
  529. {
  530. size_t len = atol( pszValue );
  531. if ( pRC && len )
  532. {
  533. pRC->nBytesTotal = len;
  534. pRC->data = (byte*)malloc( len );
  535. }
  536. }
  537. return size * nmemb;
  538. }
  539. // we're going to abuse this by using it for proxy pac fetching
  540. // the cacheData field will hold the URL of the PAC, and the data
  541. // field the contents of the pac
  542. RequestContext_t g_pacRequestCtx;
  543. // system specific headers for proxy configuration
  544. #if defined(OSX)
  545. #include <CoreFoundation/CoreFoundation.h>
  546. #include <CoreServices/CoreServices.h>
  547. #include <SystemConfiguration/SystemConfiguration.h>
  548. #endif
  549. void SetProxiesForURL( CURL *hMasterCURL, const char *pszURL )
  550. {
  551. uint32 uProxyPort = 0;
  552. char rgchProxyHost[1024];
  553. char *pszProxyExceptionList = NULL;
  554. rgchProxyHost[0] = '\0';
  555. #if defined(OSX)
  556. // create an urlref around the raw URL
  557. CFURLRef url = CFURLCreateWithBytes( NULL, ( const UInt8 * ) pszURL, strlen( pszURL ), kCFStringEncodingASCII, NULL );
  558. // copy the proxies dictionary
  559. CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
  560. // and ask the system what proxies it thinks I should consider for the given URL
  561. CFArrayRef proxies = CFNetworkCopyProxiesForURL( url, proxyDict );
  562. CFIndex iProxy;
  563. // walk through the returned set, looking for any types we (and lib curl) can handle
  564. // the list is returned in "preference order", but we can only handle http, and pac urls
  565. for( iProxy = 0; iProxy < CFArrayGetCount( proxies ); iProxy++ )
  566. {
  567. CFDictionaryRef proxy = (CFDictionaryRef) CFArrayGetValueAtIndex( proxies, iProxy );
  568. if ( proxy == NULL )
  569. break;
  570. // what type of proxy is this one?
  571. CFStringRef proxyType = (CFStringRef) CFDictionaryGetValue( proxy, kCFProxyTypeKey );
  572. if ( CFEqual( proxyType, kCFProxyTypeNone ) )
  573. {
  574. // no proxy should be used - we're done.
  575. break;
  576. }
  577. else if ( CFEqual( proxyType, kCFProxyTypeHTTP ) )
  578. {
  579. // manually configured HTTP proxy settings.
  580. const void *val = NULL;
  581. // grab the proxy port
  582. val = CFDictionaryGetValue( proxy, kCFProxyPortNumberKey );
  583. if ( val == NULL || !CFNumberGetValue( (CFNumberRef) val, kCFNumberIntType, &uProxyPort ) )
  584. // either we failed, or the port was invalid
  585. continue;
  586. // no port specified - use the default http port
  587. if ( uProxyPort == 0 )
  588. uProxyPort = 80;
  589. int cbOffset = 0;
  590. // see if they've specified authentication (username/password)
  591. val = CFDictionaryGetValue( proxy, kCFProxyUsernameKey );
  592. if ( val != NULL && CFStringGetCString( (CFStringRef) val, rgchProxyHost + cbOffset, sizeof(rgchProxyHost) - cbOffset, kCFStringEncodingASCII ) && rgchProxyHost[cbOffset] != '\0' )
  593. {
  594. // we've got "username" in rgchProxyHost
  595. cbOffset = Q_strlen( rgchProxyHost );
  596. val = CFDictionaryGetValue( proxy, kCFProxyPasswordKey );
  597. if ( val != NULL && CFStringGetLength( (CFStringRef) val ) )
  598. {
  599. // and there's a non-null password value - put a colon after username
  600. rgchProxyHost[cbOffset++] = ':';
  601. CFStringGetCString( (CFStringRef) val, rgchProxyHost + cbOffset, sizeof(rgchProxyHost) - cbOffset, kCFStringEncodingASCII );
  602. // now we've got user:password in rgchProxyHost
  603. cbOffset = Q_strlen( rgchProxyHost );
  604. }
  605. // since we've got at least a username, we need an @
  606. rgchProxyHost[cbOffset++] = '@';
  607. }
  608. val = CFDictionaryGetValue( proxy, kCFProxyHostNameKey );
  609. if ( val == NULL || !CFStringGetCString( (CFStringRef) val, rgchProxyHost + cbOffset, sizeof(rgchProxyHost) - cbOffset, kCFStringEncodingASCII ) || rgchProxyHost[cbOffset] == '\0' )
  610. continue;
  611. break;
  612. }
  613. else if ( CFEqual( proxyType, kCFProxyTypeAutoConfigurationURL ) )
  614. {
  615. // a proxy autoconfig URL has been provided
  616. char rgchPacURL[1024];
  617. // get the url (as an urlref) and turn it into a string
  618. CFURLRef cfUrl = (CFURLRef) CFDictionaryGetValue( proxy, kCFProxyAutoConfigurationURLKey );
  619. CFStringGetCString( (CFStringRef) CFStringCreateWithFormat( NULL, NULL, CFSTR("%@"), cfUrl ),
  620. rgchPacURL, sizeof( rgchPacURL ), kCFStringEncodingASCII );
  621. CURLcode res = CURLE_OK;
  622. // see if we've not yet fetched this pac file
  623. if ( !g_pacRequestCtx.cacheData || Q_strcmp( (const char *)g_pacRequestCtx.cacheData, rgchPacURL ) )
  624. {
  625. if ( g_pacRequestCtx.cacheData )
  626. {
  627. free( g_pacRequestCtx.cacheData );
  628. g_pacRequestCtx.cacheData = NULL;
  629. }
  630. if ( g_pacRequestCtx.data )
  631. {
  632. free( g_pacRequestCtx.data );
  633. g_pacRequestCtx.data = NULL;
  634. }
  635. // grab the data, using the same request context structure (and callbacks) we use for real downloads
  636. CURL *hCURL = curl_easy_init();
  637. if ( !hCURL )
  638. {
  639. AssertMsg( hCURL, "failed to initialize curl handle" );
  640. break;
  641. }
  642. curl_easy_setopt( hCURL, CURLOPT_NOPROGRESS, 1 );
  643. curl_easy_setopt( hCURL, CURLOPT_NOSIGNAL, 1 );
  644. curl_easy_setopt( hCURL, CURLOPT_CONNECTTIMEOUT, 30 );
  645. curl_easy_setopt( hCURL, CURLOPT_FOLLOWLOCATION, 1 ); // follow 30x redirections from the web server
  646. // and setup the callback fns
  647. curl_easy_setopt( hCURL, CURLOPT_HEADERFUNCTION, &curlHeaderFn );
  648. curl_easy_setopt( hCURL, CURLOPT_WRITEFUNCTION, &curlWriteFn );
  649. // setup callback stream pointers
  650. curl_easy_setopt( hCURL, CURLOPT_WRITEHEADER, &g_pacRequestCtx );
  651. curl_easy_setopt( hCURL, CURLOPT_WRITEDATA, &g_pacRequestCtx );
  652. curl_easy_setopt( hCURL, CURLOPT_URL, rgchPacURL );
  653. res = curl_easy_perform( hCURL );
  654. curl_easy_cleanup( hCURL );
  655. }
  656. if ( res == CURLE_OK )
  657. {
  658. // copy the URL into the "pac cache", if necessary
  659. if ( !g_pacRequestCtx.cacheData )
  660. {
  661. g_pacRequestCtx.cacheData = (unsigned char*) malloc( Q_strlen( rgchPacURL ) + 1 );
  662. Q_memcpy( g_pacRequestCtx.cacheData, rgchPacURL, Q_strlen( rgchPacURL ) );
  663. }
  664. if ( !g_pacRequestCtx.data ) // no data in the proxy.pac they have, so just ignore it
  665. return;
  666. // wrap the data (the pac contents) into a cfstring
  667. CFStringRef cfPacStr = CFStringCreateWithCString( kCFAllocatorDefault, (const char *)g_pacRequestCtx.data, kCFStringEncodingASCII );
  668. // and ask the system, given this proxy pac, what (list of) proxies should I consider for this URL?
  669. CFErrorRef err;
  670. CFArrayRef proxiesForUrl = CFNetworkCopyProxiesForAutoConfigurationScript( cfPacStr, cfUrl, &err );
  671. if ( proxiesForUrl )
  672. {
  673. // we're re-assigning the value that the loop is iterating over, the postincrement will fire after we do this,
  674. // hence the -1 (rather than 0) assignment to iProxy
  675. proxies = proxiesForUrl;
  676. iProxy = -1;
  677. }
  678. continue;
  679. }
  680. else
  681. {
  682. if ( g_pacRequestCtx.cacheData )
  683. {
  684. free( g_pacRequestCtx.cacheData );
  685. g_pacRequestCtx.cacheData = NULL;
  686. }
  687. }
  688. }
  689. else
  690. {
  691. Msg( "unsupported proxy type\n" );
  692. break;
  693. }
  694. }
  695. #else
  696. #warning "CHTTPDownloadThread doesn't know how to set proxy config"
  697. #endif
  698. if ( rgchProxyHost[0] == '\0' || uProxyPort <= 0 )
  699. {
  700. if ( pszProxyExceptionList )
  701. free( pszProxyExceptionList );
  702. return;
  703. }
  704. curl_easy_setopt( hMasterCURL, CURLOPT_PROXY, rgchProxyHost );
  705. curl_easy_setopt( hMasterCURL, CURLOPT_PROXYPORT, uProxyPort );
  706. if ( pszProxyExceptionList )
  707. {
  708. curl_easy_setopt( hMasterCURL, (CURLoption) (CURLOPTTYPE_OBJECTPOINT + 177) /*CURLOPT_NOPROXY*/ , pszProxyExceptionList );
  709. free( pszProxyExceptionList );
  710. }
  711. }
  712. void DownloadThread( void *voidPtr )
  713. {
  714. static bool bDoneInit = false;
  715. if ( !bDoneInit )
  716. {
  717. bDoneInit = true;
  718. curl_global_init( CURL_GLOBAL_SSL );
  719. }
  720. RequestContext_t& rc = *(RequestContext_t *)voidPtr;
  721. rc.status = HTTP_FETCH;
  722. CURL *hCURL = curl_easy_init();
  723. if ( !hCURL )
  724. {
  725. rc.error = HTTP_ERROR_INVALID_URL;
  726. rc.status = HTTP_ERROR;
  727. rc.threadDone = true;
  728. return;
  729. }
  730. curl_easy_setopt( hCURL, CURLOPT_NOPROGRESS, 1 );
  731. curl_easy_setopt( hCURL, CURLOPT_NOSIGNAL, 1 );
  732. curl_easy_setopt( hCURL, CURLOPT_CONNECTTIMEOUT, 30 );
  733. curl_easy_setopt( hCURL, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
  734. // turn off certificate verification similar to how we set INTERNET_FLAG_IGNORE_CERT_CN_INVALID and INTERNET_FLAG_IGNORE_CERT_DATE_INVALID on Windows
  735. curl_easy_setopt( hCURL, CURLOPT_SSL_VERIFYHOST, 0 );
  736. curl_easy_setopt( hCURL, CURLOPT_SSL_VERIFYPEER, 0 );
  737. // and now the callback fns
  738. curl_easy_setopt( hCURL, CURLOPT_HEADERFUNCTION, &curlHeaderFn );
  739. curl_easy_setopt( hCURL, CURLOPT_WRITEFUNCTION, &curlWriteFn );
  740. uint32 cubURL = Q_strlen( rc.baseURL ) + Q_strlen( rc.urlPath ) + 2 /*one for the /, one for the null*/;
  741. char *pchURL = (char *) malloc( cubURL );
  742. Q_snprintf( pchURL, cubURL, "%s%s", rc.baseURL, rc.urlPath );
  743. uint32 cubReferer = 0;
  744. char *pchReferer = NULL;
  745. if ( *rc.serverURL )
  746. {
  747. cubReferer = Q_strlen( rc.serverURL ) + 8;
  748. pchReferer = (char *) malloc( cubReferer );
  749. Q_snprintf( pchReferer, cubURL, "hl2://%s", rc.serverURL );
  750. }
  751. // setup proxies
  752. SetProxiesForURL( hCURL, pchURL );
  753. // set the url
  754. curl_easy_setopt( hCURL, CURLOPT_URL, pchURL );
  755. // setup callback stream pointers
  756. curl_easy_setopt( hCURL, CURLOPT_WRITEHEADER, &rc );
  757. curl_easy_setopt( hCURL, CURLOPT_WRITEDATA, &rc );
  758. curl_easy_setopt( hCURL, CURLOPT_FOLLOWLOCATION, 1 );
  759. curl_easy_setopt( hCURL, CURLOPT_MAXREDIRS, 1 );
  760. curl_easy_setopt( hCURL, CURLOPT_UNRESTRICTED_AUTH, 1 );
  761. curl_easy_setopt( hCURL, CURLOPT_USERAGENT, "Half-Life 2" );
  762. if ( pchReferer )
  763. {
  764. curl_easy_setopt( hCURL, CURLOPT_REFERER, pchReferer );
  765. }
  766. // g0g0g0
  767. CURLcode res = curl_easy_perform( hCURL );
  768. free( pchURL );
  769. if ( pchReferer )
  770. {
  771. free( pchReferer );
  772. }
  773. if ( res == CURLE_OK )
  774. {
  775. curl_easy_getinfo( hCURL , CURLINFO_RESPONSE_CODE , &rc.status );
  776. if ( rc.status == HTTPStatus_t(200) || rc.status == HTTPStatus_t(206) )
  777. {
  778. // write the file before we change the status to DONE, so that the write
  779. // will finish before the main thread goes on and starts messing with the file
  780. WriteFileFromRequestContext( rc );
  781. rc.status = HTTP_DONE;
  782. rc.error = HTTP_ERROR_NONE;
  783. }
  784. else
  785. {
  786. rc.status = HTTP_ERROR;
  787. rc.error = HTTP_ERROR_FILE_NONEXISTENT;
  788. }
  789. }
  790. else
  791. {
  792. rc.status = HTTP_ERROR;
  793. }
  794. // wait until the main thread says we can go away (so it can look at rc.data).
  795. while ( !rc.shouldStop )
  796. {
  797. ThreadSleep( 100 );
  798. }
  799. // Delete rc.data, which was allocated in this thread
  800. if ( rc.data != NULL )
  801. {
  802. delete[] rc.data;
  803. rc.data = NULL;
  804. }
  805. curl_easy_cleanup( hCURL );
  806. rc.threadDone = true;
  807. }
  808. #else
  809. void DownloadThread( void *voidPtr )
  810. {
  811. #warning "DownloadThread Not Implemented"
  812. Assert( !"Implement me" );
  813. }
  814. #endif