Counter Strike : Global Offensive Source Code
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.

919 lines
28 KiB

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