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.

406 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "sv_publishtest.h"
  5. #include "spew.h"
  6. #include "replay/replayutils.h"
  7. #include "replaysystem.h"
  8. #include "sv_basejob.h"
  9. #include "sv_fileservercleanup.h"
  10. #include "tier1/convar.h"
  11. #include "sv_filepublish.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. //----------------------------------------------------------------------------------------
  15. const char *g_pAcceptableFileserverProtocols[] = { "http", "https", NULL };
  16. const char *g_pAcceptableOffloadProtocols[] = { "ftp", NULL };
  17. //----------------------------------------------------------------------------------------
  18. class CPublishTester
  19. {
  20. public:
  21. CPublishTester();
  22. ~CPublishTester();
  23. bool Go();
  24. private:
  25. bool Test_Emptyness( const char *pDescription, const char *pStr, bool bPrintResult );
  26. bool Test_Hostname( const char *pHostname, const char *pProtocolExample );
  27. bool Test_Protocol( const char *pDescription, const char *pProtocol, const char **pAcceptableProtocols );
  28. bool Test_Port( int nPort );
  29. bool Test_Path( const char *pDescription, const char *pPath, bool bForwardSlashesAllowed, bool bBackslashesAllowed );
  30. bool Test_LocalWebServerCVars();
  31. bool Test_IO( const char *pFilename );
  32. bool Test_FilePublish( const char *pFilename, bool bOffload );
  33. bool Test_PublishedFileDelete( const char *pFullFilename, const char *pFilename, bool bOffload );
  34. bool Test_WaitingForPlayersCVar();
  35. void PrintBaseUrlWarning();
  36. char *m_pGarbageBuffer;
  37. CBaseJob *m_pJob;
  38. CBaseJob *m_pCleanerJob;
  39. };
  40. //----------------------------------------------------------------------------------------
  41. #define GARBAGE_BUFFER_SIZE ( 1024 * 1000 )
  42. CPublishTester::CPublishTester()
  43. : m_pGarbageBuffer( NULL ),
  44. m_pJob( NULL ),
  45. m_pCleanerJob( NULL )
  46. {
  47. m_pGarbageBuffer = new char[ GARBAGE_BUFFER_SIZE ];
  48. }
  49. CPublishTester::~CPublishTester()
  50. {
  51. delete [] m_pGarbageBuffer;
  52. if ( m_pJob )
  53. {
  54. m_pJob->Release();
  55. }
  56. if ( m_pCleanerJob )
  57. {
  58. m_pCleanerJob->Release();
  59. }
  60. }
  61. bool CPublishTester::Test_Hostname( const char *pHostname, const char *pProtocolExample )
  62. {
  63. if ( !Test_Emptyness( "Hostname", pHostname, false ) )
  64. return false;
  65. if ( V_strstr( pHostname, "://" ) )
  66. {
  67. g_pBlockSpewer->PrintEventResult( false );
  68. CFmtStr fmtError( "Should not contain a protocol (e.g: %s)!", pProtocolExample );
  69. g_pBlockSpewer->PrintEventError( fmtError.Access() );
  70. return false;
  71. }
  72. // Test IP lookup
  73. char szIP[16];
  74. if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, szIP, sizeof( szIP ) ) )
  75. {
  76. g_pBlockSpewer->PrintEventResult( false );
  77. g_pBlockSpewer->PrintEventError( "DNS lookup failed!" );
  78. return false;
  79. }
  80. g_pBlockSpewer->PrintEventResult( true );
  81. g_pBlockSpewer->PrintEmptyLine();
  82. return true;
  83. }
  84. bool CPublishTester::Test_Emptyness( const char *pDescription, const char *pStr, bool bPrintResult )
  85. {
  86. g_pBlockSpewer->PrintValue( pDescription, pStr );
  87. g_pBlockSpewer->PrintEventStartMsg( "Validating" );
  88. if ( V_strlen( pStr ) == 0 )
  89. {
  90. g_pBlockSpewer->PrintEventResult( false );
  91. g_pBlockSpewer->PrintEventError( "Empty!" );
  92. return false;
  93. }
  94. if ( bPrintResult )
  95. {
  96. g_pBlockSpewer->PrintEventResult( true );
  97. g_pBlockSpewer->PrintEmptyLine();
  98. }
  99. return true;
  100. }
  101. bool CPublishTester::Test_Port( int nPort )
  102. {
  103. g_pBlockSpewer->PrintValue( "Port", Replay_va( "%i", nPort ) );
  104. g_pBlockSpewer->PrintEventStartMsg( "Validating" );
  105. if ( nPort < 0 || nPort > 65535 )
  106. {
  107. g_pBlockSpewer->PrintEventResult( false );
  108. g_pBlockSpewer->PrintEventError( "Port must be between 0 and 65535." );
  109. return false;
  110. }
  111. g_pBlockSpewer->PrintEventResult( true );
  112. g_pBlockSpewer->PrintEmptyLine();
  113. return true;
  114. }
  115. bool CPublishTester::Test_Path( const char *pDescription, const char *pPath, bool bForwardSlashesAllowed, bool bBackslashesAllowed )
  116. {
  117. g_pBlockSpewer->PrintValue( pDescription, pPath );
  118. g_pBlockSpewer->PrintEventStartMsg( "Validating" );
  119. if ( V_strlen( pPath ) == 0 )
  120. {
  121. g_pBlockSpewer->PrintEventResult( false );
  122. g_pBlockSpewer->PrintEventError( "Empty path not allowed." );
  123. return false;
  124. }
  125. if ( !bBackslashesAllowed && V_strstr( pPath, "\\" ) )
  126. {
  127. g_pBlockSpewer->PrintEventResult( false );
  128. g_pBlockSpewer->PrintEventError( "Backslashes not allowed!" );
  129. return false;
  130. }
  131. if ( !bForwardSlashesAllowed && V_strstr( pPath, "/" ) )
  132. {
  133. g_pBlockSpewer->PrintEventResult( false );
  134. g_pBlockSpewer->PrintEventError( "Forward slashes not allowed!" );
  135. return false;
  136. }
  137. if ( V_strstr( pPath, "//" ) || V_strstr( pPath, "\\\\" ) )
  138. {
  139. g_pBlockSpewer->PrintEventResult( false );
  140. g_pBlockSpewer->PrintEventError( "Double slash detected!" );
  141. return false;
  142. }
  143. if ( V_strstr( pPath, ".." ) )
  144. {
  145. g_pBlockSpewer->PrintEventResult( false );
  146. g_pBlockSpewer->PrintEventError( "\"..\" not allowed!" );
  147. return false;
  148. }
  149. g_pBlockSpewer->PrintEventResult( true );
  150. g_pBlockSpewer->PrintEmptyLine();
  151. return true;
  152. }
  153. bool CPublishTester::Test_Protocol( const char *pDescription, const char *pProtocol, const char **pAcceptableProtocols )
  154. {
  155. g_pBlockSpewer->PrintValue( pDescription, pProtocol );
  156. g_pBlockSpewer->PrintEventStartMsg( "Validating" );
  157. // Test to see if the input protocol is acceptable
  158. bool bProtocolOK = false;
  159. int i = 0;
  160. while ( pAcceptableProtocols[ i ] )
  161. {
  162. if ( V_strcmp( pAcceptableProtocols[ i++ ], pProtocol ) == 0 )
  163. {
  164. bProtocolOK = true;
  165. break;
  166. }
  167. }
  168. // Protocol allowed?
  169. if ( !bProtocolOK )
  170. {
  171. g_pBlockSpewer->PrintEventResult( false );
  172. CFmtStr fmtError( "Must be one of the following (case-sensitive): " );
  173. i = 0;
  174. while ( pAcceptableProtocols[ i ] )
  175. fmtError.AppendFormat( "\"%s\" ", pAcceptableProtocols[ i++ ] );
  176. g_pBlockSpewer->PrintEventError( fmtError.Access() );
  177. return false;
  178. }
  179. g_pBlockSpewer->PrintEventResult( true );
  180. g_pBlockSpewer->PrintEmptyLine();
  181. return true;
  182. }
  183. bool CPublishTester::Test_LocalWebServerCVars()
  184. {
  185. // NOTE: We use the raw cvar here as opposed to CServerReplayContext::GetLocalFileServerPath(),
  186. // which actually fixes slashes. If the cvar is using incorrect slashes here for the given OS,
  187. // this test will fail with a specific error message.
  188. extern ConVar replay_local_fileserver_path;
  189. if ( !Test_Path( "Path", replay_local_fileserver_path.GetString(), IsPosix(), IsWindows() ) )
  190. return false;
  191. return true;
  192. }
  193. bool CPublishTester::Test_IO( const char *pFilename )
  194. {
  195. g_pBlockSpewer->PrintTestHeader( "Testing File I/O" );
  196. // Print out temp directory so the context for this section is clear
  197. g_pBlockSpewer->PrintValue( "Temp path", SV_GetTmpDir() );
  198. g_pBlockSpewer->PrintEmptyLine();
  199. // Open the file
  200. FileHandle_t hTmpFile = g_pFullFileSystem->Open( pFilename, "wb+" );
  201. g_pBlockSpewer->PrintEventStartMsg( "Opening temp file" );
  202. if ( !hTmpFile )
  203. {
  204. g_pBlockSpewer->PrintEventResult( false );
  205. return false;
  206. }
  207. g_pBlockSpewer->PrintEventResult( true );
  208. // Write the file
  209. g_pBlockSpewer->PrintEventStartMsg( "Allocating test buffer" ); // Lie.
  210. if ( !m_pGarbageBuffer )
  211. {
  212. g_pBlockSpewer->PrintEventResult( false );
  213. return false;
  214. }
  215. g_pBlockSpewer->PrintEventResult( true );
  216. g_pBlockSpewer->PrintEventStartMsg( "Writing temp file" );
  217. if ( g_pFullFileSystem->Write( m_pGarbageBuffer, GARBAGE_BUFFER_SIZE, hTmpFile ) != GARBAGE_BUFFER_SIZE )
  218. {
  219. g_pBlockSpewer->PrintEventResult( false );
  220. return false;
  221. }
  222. g_pBlockSpewer->PrintEventResult( true );
  223. // Close the file
  224. g_pFullFileSystem->Close( hTmpFile );
  225. return true;
  226. }
  227. bool CPublishTester::Test_FilePublish( const char *pFilename, bool bOffload )
  228. {
  229. g_pBlockSpewer->PrintTestHeader( "Testing file publisher" );
  230. g_pBlockSpewer->PrintValue( "Fileserver type", "Local Web server" );
  231. if ( !Test_LocalWebServerCVars() )
  232. return false;
  233. m_pJob = SV_CreateLocalPublishJob( pFilename );
  234. g_pBlockSpewer->PrintEmptyLine();
  235. // Run publish test
  236. if ( !m_pJob || !SV_RunJobToCompletion( m_pJob ) )
  237. {
  238. g_pBlockSpewer->PrintEventError( m_pJob->GetErrorStr() );
  239. return false;
  240. }
  241. return true;
  242. }
  243. bool CPublishTester::Test_PublishedFileDelete( const char *pFullFilename, const char *pFilename, bool bOffload )
  244. {
  245. g_pBlockSpewer->PrintTestHeader( "Testing fileserver delete" );
  246. if ( bOffload )
  247. {
  248. // Delete the file from the tmp dir
  249. g_pFullFileSystem->RemoveFile( pFullFilename );
  250. }
  251. m_pCleanerJob = SV_CreateDeleteFileJob();
  252. IFileserverCleanerJob *pCleanerJobImp = SV_CastJobToIFileserverCleanerJob( m_pCleanerJob );
  253. pCleanerJobImp->AddFileForDelete( pFilename );
  254. if ( !m_pCleanerJob || !SV_RunJobToCompletion( m_pCleanerJob ) )
  255. {
  256. g_pBlockSpewer->PrintEventError( m_pCleanerJob->GetErrorStr() );
  257. return false;
  258. }
  259. return true;
  260. }
  261. bool CPublishTester::Test_WaitingForPlayersCVar()
  262. {
  263. ConVarRef mp_waitingforplayers_cancel( "mp_waitingforplayers_cancel" );
  264. if ( mp_waitingforplayers_cancel.IsValid() && mp_waitingforplayers_cancel.GetBool() )
  265. {
  266. g_pBlockSpewer->PrintEventError( "mp_waitingforplayers_cancel must be 0 in order for replay to work!" );
  267. return false;
  268. }
  269. return true;
  270. }
  271. void CPublishTester::PrintBaseUrlWarning()
  272. {
  273. g_pBlockSpewer->PrintEmptyLine();
  274. g_pBlockSpewer->PrintMsg( "If clients can't access the following URL via a Web" );
  275. g_pBlockSpewer->PrintMsg( "browser, they will not be able to download Replays." );
  276. g_pBlockSpewer->PrintEmptyLine();
  277. g_pBlockSpewer->PrintValue( "URL", Replay_GetDownloadURL() );
  278. }
  279. bool CPublishTester::Go()
  280. {
  281. const bool bOffload = false;
  282. // Force anyone outside of Go() to use the block spewer until we're done.
  283. CSpewScope SpewScope( g_pBlockSpewer );
  284. g_pBlockSpewer->PrintMsg( "TESTING REPLAY SYSTEM CONFIGURATION..." );
  285. // Fileserver convars
  286. g_pBlockSpewer->PrintTestHeader( "Testing Fileserver ConVars (replay_fileserver_*)" );
  287. // Test replay_fileserver_protocol
  288. extern ConVar replay_fileserver_protocol;
  289. if ( !Test_Protocol( "Protocol", replay_fileserver_protocol.GetString(), g_pAcceptableFileserverProtocols ) )
  290. return false;
  291. extern ConVar replay_fileserver_host;
  292. if ( !Test_Hostname( replay_fileserver_host.GetString(), "\"http\" or \"https\"" ) )
  293. return false;
  294. extern ConVar replay_fileserver_port;
  295. if ( !Test_Port( replay_fileserver_port.GetInt() ) )
  296. return false;
  297. extern ConVar replay_fileserver_path;
  298. if ( !Test_Path( "Path", replay_fileserver_path.GetString(), true, false ) )
  299. return false;
  300. // Print out the base URL / warning
  301. PrintBaseUrlWarning();
  302. CFmtStr fmtTmpFilename( "testpublish_%i.tmp", (int)abs( rand() % 10000 ) );
  303. CFmtStr fmtTmpFilenameFullPath( "%s%s", SV_GetTmpDir(), fmtTmpFilename.Access() );
  304. const char *pFilename = fmtTmpFilenameFullPath.Access();
  305. // Test file I/O
  306. if ( !Test_IO( pFilename ) )
  307. return false;
  308. // Get out if necessary
  309. if ( !Test_FilePublish( pFilename, bOffload ) )
  310. return false;
  311. // Test delete from fileserver
  312. if ( !Test_PublishedFileDelete( pFilename, fmtTmpFilename.Access(), bOffload ) )
  313. return false;
  314. // Make sure mp_waitingforplayers_cancel isn't on or replay will be fucked.
  315. if ( !Test_WaitingForPlayersCVar() )
  316. return false;
  317. return true;
  318. }
  319. //----------------------------------------------------------------------------------------
  320. bool SV_DoTestPublish()
  321. {
  322. CPublishTester tester;
  323. return tester.Go();
  324. }
  325. //----------------------------------------------------------------------------------------