1 /////////////////////////////////////////
2 //
3 //   OpenLieroX
4 //
5 //   Auxiliary Software class library
6 //
7 //   based on the work of JasonB
8 //   enhanced by Dark Charlie and Albert Zeyer
9 //
10 //   code under LGPL
11 //
12 /////////////////////////////////////////
13 
14 
15 // File downloading over HTTP
16 // Created 13/10/07
17 // By Karel Petranek, Albert Zeyer and Martin Griffin
18 
19 #ifdef _MSC_VER
20 // TODO: can't you put this into your IDE settings? it does not belong here
21 #pragma warning(disable: 4786)  // WARNING: identifier XXX was truncated to 255 characters in the debug info
22 #pragma warning(disable: 4503)  // WARNING: decorated name length exceeded, name was truncated
23 #endif
24 
25 
26 #include "LieroX.h"
27 #include "Options.h"
28 #include "Debug.h"
29 #include "StringUtils.h"
30 #include "FindFile.h"
31 #include "EndianSwap.h"
32 #include "FileDownload.h"
33 #include "MathLib.h"
34 
35 
36 
37 
38 //
39 // Download error strings
40 //
41 const std::string sDownloadErrors[] =  {
42 	"No error",
43 	"No file specified",
44 	"No destination specified",
45 	"No servers available",
46 	"HTTP error",
47 	"Error while saving"
48 };
49 
50 //
51 // Single file download
52 //
53 
54 //////////////////
55 // Start the transfer
Start(const std::string & filename,const std::string & dest_dir)56 void CHttpDownloader::Start(const std::string& filename, const std::string& dest_dir)
57 {
58 	Lock();
59 
60 	// Checks
61 	if (filename.size() == 0)  {
62 		Unlock();
63 		SetDlError(FILEDL_ERROR_NO_FILE);
64 		return;
65 	}
66 
67 	if (dest_dir.size() == 0)  {
68 		Unlock();
69 		SetDlError(FILEDL_ERROR_NO_DEST);
70 		return;
71 	}
72 
73 	if (tDownloadServers == NULL)  {
74 		Unlock();
75 		SetDlError(FILEDL_ERROR_NO_SERVER);
76 		return;
77 	}
78 
79 	if (tDownloadServers->empty())  {
80 		Unlock();
81 		SetDlError(FILEDL_ERROR_NO_SERVER);
82 		return;
83 	}
84 
85 	// Stop & clear any previous download
86 	Unlock();
87 	Stop();
88 	Lock();
89 
90 	// Fill in the info
91 	sFileName = filename;
92 	if (*dest_dir.rbegin() != '/' && *dest_dir.rbegin() != '\\')
93 		sDestPath = dest_dir + "/" + filename;
94 	else
95 		sDestPath = dest_dir + filename;
96 
97 	// Create the file
98 	tFile = OpenGameFile(sDestPath, "wb");
99 	if (tFile == NULL)  {
100 		Unlock();
101 		SetDlError(FILEDL_ERROR_SAVING);
102 		return;
103 	}
104 
105 	// Try to download from first server in the list
106 	iCurrentServer = 0;
107 	tHttp.RequestData((*tDownloadServers)[iCurrentServer] + UrlEncode(filename), tLXOptions->sHttpProxy);
108 
109 	// Set the state to initializing
110 	iState = FILEDL_INITIALIZING;
111 
112 	Unlock();
113 }
114 
115 /////////////////
116 // Stop the transfer and clear everything
Stop()117 void CHttpDownloader::Stop()
118 {
119 	Lock();
120 
121 	// Cancel the transfer
122 	if (tHttp.RequestedData())
123 		tHttp.CancelProcessing();
124 
125 	// Clear
126 	if (tFile)  {
127 		fclose(tFile);
128 		remove(GetFullFileName(sDestPath).c_str());
129 	}
130 	sFileName = "";
131 	sDestPath = "";
132 	tFile = NULL;
133 	iCurrentServer = 0;
134 	iState = FILEDL_NO_FILE;
135 
136 	Unlock();
137 
138 	SetDlError(FILEDL_ERROR_NO_ERROR);
139 }
140 
141 /////////////////
142 // Process the downloading
ProcessDownload()143 void CHttpDownloader::ProcessDownload()
144 {
145 	Lock();
146 
147 	// Check that there has been no error yet
148 	if (tError.iError != FILEDL_ERROR_NO_ERROR)  {
149 		Unlock();
150 		return;
151 	}
152 
153 	// Process the HTTP
154 	int res = tHttp.ProcessRequest();
155 
156 	switch (res)  {
157 	// Still processing
158 	case HTTP_PROC_PROCESSING:
159 		iState = FILEDL_RECEIVING;
160 
161 		// If we already received something, put it in the file
162 		if (tHttp.GetData().length() > 0 && tFile && !tHttp.IsRedirecting())  {
163 			if (fwrite(tHttp.GetData().data(), tHttp.GetData().length(), 1, tFile) == 1)
164 				tHttp.ClearReceivedData(); // Save memory
165 		}
166 		break;
167 
168 	// Error
169 	case HTTP_PROC_ERROR:
170 		// If the file could not be found, try another server
171 		if (tHttp.GetError().iError == HTTP_FILE_NOT_FOUND)  {
172 			iCurrentServer++;
173 			if ((size_t)iCurrentServer < tDownloadServers->size())  {
174 				tHttp.RequestData((*tDownloadServers)[iCurrentServer] + sFileName, tLXOptions->sHttpProxy);  // Request the file
175 				iState = FILEDL_INITIALIZING;
176 				break;  // Don't set the error
177 			}
178 		}
179 
180 		// Delete the file
181 		if (tFile)  {
182 			fclose(tFile);
183 			tFile = NULL;
184 			remove(GetFullFileName(sDestPath).c_str());
185 		}
186 
187 		Unlock();
188 		SetHttpError(tHttp.GetError());
189 		Lock();
190 		iState = FILEDL_ERROR;
191 
192 		break;
193 
194 	// Finished
195 	case HTTP_PROC_FINISHED:
196 		iState = FILEDL_FINISHED;
197 
198 		// Save the data in the file
199 		if (tFile)  {
200 			if (tHttp.GetData().size() > 0)
201 				if (fwrite(tHttp.GetData().data(), tHttp.GetData().size(), 1, tFile) != 1)
202 					SetDlError(FILEDL_ERROR_SAVING);
203 			fclose(tFile);
204 			tFile = NULL;
205 		} else {
206 			Unlock();
207 			SetDlError(FILEDL_ERROR_SAVING);
208 		}
209 
210 		break;
211 	}
212 
213 	Unlock();
214 }
215 
216 /////////////////
217 // Set downloading error
SetDlError(int id)218 void CHttpDownloader::SetDlError(int id)
219 {
220 	Lock();
221 
222 	tError.iError = id;
223 	tError.sErrorMsg = sDownloadErrors[id];
224 	tError.tHttpError = tHttp.GetError();
225 
226 	if (id != FILEDL_ERROR_NO_ERROR)
227 		warnings << "HTTP Download Error: " << tError.sErrorMsg << endl;
228 
229 	Unlock();
230 }
231 
232 /////////////////
233 // Set downloading error (HTTP problem)
SetHttpError(HttpError err)234 void CHttpDownloader::SetHttpError(HttpError err)
235 {
236 	Lock();
237 
238 	tError.iError = FILEDL_ERROR_HTTP;
239 	tError.sErrorMsg = "HTTP Error: " + err.sErrorMsg;
240 	tError.tHttpError.iError = err.iError;
241 	tError.tHttpError.sErrorMsg = err.sErrorMsg;
242 
243 	if (err.iError != HTTP_NO_ERROR)
244 		warnings << "HTTP Download Error: " << tError.sErrorMsg << endl;
245 
246 	Unlock();
247 }
248 
249 ////////////////
250 // Get the file downloading progress
GetProgress()251 int CHttpDownloader::GetProgress()
252 {
253 	Lock();
254 	int res = 0;
255 	if (tHttp.GetDataLength() != 0 && !tHttp.IsRedirecting())
256 		res = (byte)MIN((size_t)100, tHttp.GetReceivedDataLen() * 100 / tHttp.GetDataLength());
257 	Unlock();
258 
259 	return res;
260 }
261 
262 //////////////////
263 // Get the currently processed filename
GetFileName()264 std::string	CHttpDownloader::GetFileName()
265 {
266 	Lock();
267 	std::string tmp = sFileName;
268 	Unlock();
269 	return tmp;
270 }
271 
272 ////////////////////
273 // Get the current state
GetState()274 int	CHttpDownloader::GetState()
275 {
276 	Lock();
277 	int tmp = iState;
278 	Unlock();
279 	return tmp;
280 }
281 
282 ////////////////////
283 // Get the current ID
GetID()284 size_t CHttpDownloader::GetID()
285 {
286 	Lock();
287 	size_t tmp = iID;
288 	Unlock();
289 	return tmp;
290 }
291 
292 ////////////////////////
293 // Get the download error
GetError()294 DownloadError CHttpDownloader::GetError()
295 {
296 	Lock();
297 	DownloadError tmp = tError;
298 	Unlock();
299 	return tmp;
300 }
301 
302 //
303 // File downloader
304 //
305 
306 /////////////
307 // The main function for the threaded downloading
ManagerMain(void * param)308 int ManagerMain(void *param)
309 {
310 	CHttpDownloadManager *_this = (CHttpDownloadManager *)param;
311 
312 	while (!_this->bBreakThread)  {
313 		_this->ProcessDownloads();
314 
315 		// Sleep if nothing to do
316 		if (_this->iActiveDownloads == 0)
317 			// TODO: use conditions here
318 			SDL_Delay(50);
319 	}
320 
321 	return 0;
322 }
323 
324 /////////////////
325 // Constructor
CHttpDownloadManager()326 CHttpDownloadManager::CHttpDownloadManager()
327 {
328 	iActiveDownloads = 0;
329 
330 	// Load the list of available servers
331 	FILE *fp = OpenGameFile("cfg/downloadservers.txt", "r");
332 	if (fp)  {
333 		while (!feof(fp))  {
334 			std::string server = ReadUntil(fp, '\n');
335 
336 			// Check (server with less than 3 characters cannot be valid)
337 			if (server.size() < 3)
338 				continue;
339 
340 			// Delete CR character if present
341 			if (*server.rbegin() == '\r')
342 				server.erase(server.size() - 1);
343 
344 			// Trim & add the server
345 			TrimSpaces(server);
346 			tDownloadServers.push_back(server);
347 		}
348 	}
349 
350 	// Create the thread
351 	bBreakThread = false;
352 	tMutex = SDL_CreateMutex();
353 	tThread = threadPool->start(&ManagerMain, (void *)this, "CHttpDownloadManager helper");
354 }
355 
356 ///////////////
357 // Destructor
~CHttpDownloadManager()358 CHttpDownloadManager::~CHttpDownloadManager()
359 {
360 	bBreakThread = true;
361 	threadPool->wait(tThread, NULL);
362 
363 	Lock();
364 
365 	// Stop the downloads
366 	for (std::list<CHttpDownloader *>::iterator i = tDownloads.begin(); i != tDownloads.end(); i++)  {
367 		(*i)->Stop();
368 		delete (*i);
369 	}
370 	iActiveDownloads = 0;
371 
372 	Unlock();
373 
374 	SDL_DestroyMutex(tMutex);
375 }
376 
377 //////////////
378 // Add and start a download
StartFileDownload(const std::string & filename,const std::string & dest_dir)379 void CHttpDownloadManager::StartFileDownload(const std::string& filename, const std::string& dest_dir)
380 {
381 	Lock();
382 
383 	// Add and start the download
384 	CHttpDownloader *new_dl = new CHttpDownloader(&tDownloadServers, tDownloads.size());
385 	new_dl->Start(filename, dest_dir);
386 
387 	tDownloads.push_back(new_dl);
388 
389 	Unlock();
390 }
391 
392 /////////////
393 // Cancel a download
CancelFileDownload(const std::string & filename)394 void CHttpDownloadManager::CancelFileDownload(const std::string& filename)
395 {
396 	Lock();
397 
398 	// Find the download and remove it, the destructor will stop the download automatically
399 	for (std::list<CHttpDownloader *>::iterator i = tDownloads.begin(); i != tDownloads.end(); i++)
400 		if ((*i)->GetFileName() == filename)  {
401 			if ((*i)->GetState() == FILEDL_INITIALIZING || (*i)->GetState() == FILEDL_RECEIVING)
402 				iActiveDownloads = iActiveDownloads > 0 ? iActiveDownloads - 1 : 0;
403 			(*i)->Stop();
404 			delete (*i);
405 			tDownloads.erase(i);
406 			break;
407 		}
408 
409 	Unlock();
410 }
411 
RemoveFileDownload(const std::string & filename)412 void CHttpDownloadManager::RemoveFileDownload(const std::string &filename)
413 {
414 	Lock();
415 
416 	// Find the download and remove it, the destructor will stop the download automatically
417 	for (std::list<CHttpDownloader *>::iterator i = tDownloads.begin(); i != tDownloads.end(); i++)
418 		if ((*i)->GetFileName() == filename)  {
419 			if ((*i)->GetState() == FILEDL_FINISHED || (*i)->GetState() == FILEDL_ERROR)  {
420 				delete (*i);
421 				tDownloads.erase(i);
422 				break;
423 			}
424 		}
425 
426 	Unlock();
427 }
428 
429 //////////////
430 // Returns true if the file has been successfully downloaded
IsFileDownloaded(const std::string & filename)431 bool CHttpDownloadManager::IsFileDownloaded(const std::string& filename)
432 {
433 	Lock();
434 
435 	for (std::list<CHttpDownloader *>::iterator i = tDownloads.begin(); i != tDownloads.end(); i++)
436 		if ((*i)->GetFileName() == filename)  {
437 			bool finished = (*i)->GetState() == FILEDL_FINISHED;
438 			Unlock();
439 			return finished;
440 		}
441 
442 	Unlock();
443 	return false;
444 }
445 
446 //////////////
447 // Returns the download error for the specified file
FileDownloadError(const std::string & filename)448 DownloadError CHttpDownloadManager::FileDownloadError(const std::string& filename)
449 {
450 	Lock();
451 
452 	for (std::list<CHttpDownloader *>::iterator i = tDownloads.begin(); i != tDownloads.end(); i++)  {
453 		if ((*i)->GetFileName() == filename)  {
454 			Unlock();
455 			return (*i)->GetError();
456 		}
457 	}
458 
459 	Unlock();
460 
461 	// Not found, create a default "no error"
462 	DownloadError err;
463 	err.iError = FILEDL_ERROR_NO_ERROR;
464 	err.sErrorMsg = sDownloadErrors[FILEDL_ERROR_NO_ERROR];
465 	err.tHttpError.iError = HTTP_NO_ERROR;
466 	err.tHttpError.sErrorMsg = "No error";
467 	return err;
468 }
469 
470 ///////////////
471 // Get the file download progress in percents
GetFileProgress(const std::string & filename)472 int CHttpDownloadManager::GetFileProgress(const std::string& filename)
473 {
474 	Lock();
475 	for (std::list<CHttpDownloader *>::iterator i = tDownloads.begin(); i != tDownloads.end(); i++)
476 		if ((*i)->GetFileName() == filename)  {
477 			Unlock();
478 			return (*i)->GetProgress();
479 		}
480 
481 	Unlock();
482 	return 0;
483 }
484 
485 ////////////////////
486 // Process all the downloads
ProcessDownloads()487 void CHttpDownloadManager::ProcessDownloads()
488 {
489 	Lock();
490 
491 	// Process the downloads
492 	iActiveDownloads = 0;
493 	for (std::list<CHttpDownloader *>::iterator i = tDownloads.begin(); i != tDownloads.end(); i++)  {
494 		if ((*i)->GetState() == FILEDL_INITIALIZING || (*i)->GetState() == FILEDL_RECEIVING)  {
495 			iActiveDownloads++;
496 			(*i)->ProcessDownload();
497 		}
498 	}
499 
500 	Unlock();
501 }
502 
503 
504 // TODO: more comments please
505 // TODO: rename the class, it's not only downloading but also uploading
506 // It might be also used not only in game in the future
507 // Valid name will be CFileDownloaderUdp, since it's packet oriented, comments will appear someday (I hope).
508 
reset()509 void CUdpFileDownloader::reset()
510 {
511 	iPos = 0;
512 	tPrevState = S_FINISHED;
513 	tState = S_FINISHED;
514 	sFilename = "";
515 	sData = "";
516 	sLastFileRequested = "";
517 	tRequestedFiles.clear();
518 	bWasAborted = false;
519 	bWasError = false;
520 }
521 
setDataToSend(const std::string & name,const std::string & data,bool noCompress)522 void CUdpFileDownloader::setDataToSend( const std::string & name, const std::string & data, bool noCompress )
523 {
524 	if( name == "" )
525 	{
526 		notes << "CUdpFileDownloader::setDataToSend() empty file name" << endl;
527 		reset();
528 		bWasError = true;
529 		return;
530 	};
531 	tPrevState = tState;
532 	tState = S_SEND;
533 	iPos = 0;
534 	sFilename = name;
535 	std::string data1 = sFilename;
536 	data1.append( 1, '\0' );
537 	data1.append(data);
538 	Compress( data1, &sData, noCompress );
539 	notes << "CFileDownloaderInGame::setDataToSend() filename " << sFilename << " data.size() " << data.size() << " compressed " << sData.size() << endl;
540 }
541 
setFileToSend(const std::string & path)542 void CUdpFileDownloader::setFileToSend( const std::string & path )
543 {
544 	FILE * ff = OpenGameFile( path, "rb" );
545 	if( ff == NULL )
546 	{
547 		reset();
548 		bWasError = true;
549 		return;
550 	};
551 	char buf[16384];
552 	std::string data = "";
553 
554 	while( ! feof( ff ) )
555 	{
556 		size_t read = fread( buf, 1, sizeof(buf), ff );
557 		data.append( buf, read );
558 	};
559 	fclose( ff );
560 
561 	bool noCompress = false;
562 	if( stringcaserfind( path, ".png" ) != std::string::npos ||
563 		stringcaserfind( path, ".lxl" ) != std::string::npos || // LieroX levels are in .png format
564 		stringcaserfind( path, ".ogg" ) != std::string::npos ||
565 		stringcaserfind( path, ".mp3" ) != std::string::npos )
566 		noCompress = true;
567 	setDataToSend( path, data, noCompress );
568 };
569 
570 enum { MAX_DATA_CHUNK = 254 };	// UCHAR_MAX - 1, client and server should have this equal
receive(CBytestream * bs)571 bool CUdpFileDownloader::receive( CBytestream * bs )
572 {
573 	uint chunkSize = bs->readByte();
574 	if( chunkSize == 0 )	// Ping packet with zero data - do not change downloader state
575 		return false;
576 	if( tState == S_FINISHED )
577 	{
578 		tPrevState = tState;
579 		tState = S_RECEIVE;
580 		iPos = 0;
581 		sFilename = "";
582 		sData = "";
583 		notes << "CFileDownloaderInGame::receive()  started receiving " << sLastFileRequested << endl;
584 	};
585 	bool Finished = false;
586 	if( chunkSize != MAX_DATA_CHUNK )
587 	{
588 		Finished = true;
589 		if( chunkSize > MAX_DATA_CHUNK )
590 			chunkSize = MAX_DATA_CHUNK;
591 	}
592 	if( tState != S_RECEIVE )
593 	{
594 		reset();
595 		bWasError = true;
596 		std::string data, unpacked;
597 		data.append( bs->readData(chunkSize) );
598 		notes << "CFileDownloaderInGame::receive() - error, not receiving!" << endl;
599 		if( Decompress( data, &unpacked ) )
600 			if( strStartsWith(unpacked, "ABORT:" + std::string( 1, '\0' )) )
601 			{
602 				notes << "CFileDownloaderInGame::receive() - abort received" << endl;
603 				bWasAborted = true;
604 			};
605 		return true;	// Receive finished (due to error)
606 	};
607 	//notes << "CFileDownloaderInGame::receive() chunk " << chunkSize << endl;
608 	sData.append( bs->readData(chunkSize) );
609 	if( Finished )
610 	{
611 		tPrevState = tState;
612 		tState = S_FINISHED;
613 		iPos = 0;
614 		bool error = true;
615 		size_t compressedSize = sData.size(); // TODO: unused
616 		if( Decompress( sData, &sFilename ) )
617 		{
618 			error = false;
619 			std::string::size_type f = sFilename.find('\0');
620 			if( f == std::string::npos )
621 				error = true;
622 			else
623 			{
624 				sData.assign( sFilename, f+1, sFilename.size() - (f+1) );
625 				sFilename.resize( f );
626 				notes << "CFileDownloaderInGame::receive() filename " << sFilename << " data.size() " << sData.size() << " compressed " << compressedSize << endl;
627 			};
628 		};
629 		if( error )
630 		{
631 			notes << "CFileDownloaderInGame::receive() error after " << sData.size() << " bytes" << endl;
632 			reset();
633 		}
634 		bWasError = error;
635 		processFileRequests();
636 		return true;	// Receive finished
637 	};
638 	return false;
639 };
640 
send(CBytestream * bs)641 bool CUdpFileDownloader::send( CBytestream * bs )
642 {
643 	// The transferred file can be corrupted if some of the packets  gets lost,
644 	// create some sequece checking here
645 	// Don't worry about safety, we send everything zipped and it has checksum attached, missed packets -> wrong checksum.
646 	if( tState != S_SEND )
647 	{
648 		reset();
649 		bWasError = true;
650 		return true;	// Send finished (due to error)
651 	}
652 	size_t chunkSize = MIN( sData.size() - iPos, (size_t)MAX_DATA_CHUNK );
653 	if( sData.size() - iPos == MAX_DATA_CHUNK )
654 		chunkSize++; // TODO: why? it means that chunkSize > MAX_DATA_CHUNK. somewhere else it is stated that this should never be the case. even worse, it sends only MAX_DATA_CHUNK bytes
655 	bs->writeByte( (byte)chunkSize );
656 	bs->writeData( sData.substr( iPos, MIN( chunkSize, (size_t)MAX_DATA_CHUNK ) ) );
657 	iPos += chunkSize;
658 	//notes << "CFileDownloaderInGame::send() " << iPos << "/" << sData.size() << endl;
659 	if( chunkSize != MAX_DATA_CHUNK )
660 	{
661 		tPrevState = tState;
662 		tState = S_FINISHED;
663 		iPos = 0;
664 		return true;	// Send finished
665 	}
666 	return false;
667 }
668 
sendPing(CBytestream * bs) const669 void CUdpFileDownloader::sendPing( CBytestream * bs ) const
670 {
671 	bs->writeByte( 0 );
672 }
673 
allowFileRequest(bool allow)674 void CUdpFileDownloader::allowFileRequest( bool allow )
675 {
676 	bAllowFileRequest = allow;
677 }
678 
requestFile(const std::string & path,bool retryIfFail)679 void CUdpFileDownloader::requestFile( const std::string & path, bool retryIfFail )
680 {
681 	setDataToSend( "GET:", path, false );
682 	if( retryIfFail )
683 	{
684 		bool exist = false;
685 		for( size_t f = 0; f < tRequestedFiles.size(); f++ )
686 			if( tRequestedFiles[f] == path )
687 				exist = true;
688 		if( ! exist )
689 			tRequestedFiles.push_back( path );
690 	}
691 	sLastFileRequested = path;
692 }
693 
requestFilesPending()694 bool CUdpFileDownloader::requestFilesPending()
695 {
696 	if( tRequestedFiles.empty() )
697 		return false;
698 	if( ! isFinished() )
699 		return true;	// Receiving or sending in progress
700 
701 	std::string file = tRequestedFiles.back();
702 	if( sLastFileRequested == file ||
703 		( file.find("STAT:") == 0 && sLastFileRequested == file.substr( strlen("STAT:") ) ) )
704 			tRequestedFiles.pop_back();	// We already asked for that file and failed, try another files in queue if we fail this time
705 
706 	if( file.find("STAT:") == 0 )
707 		requestFileInfo( file.substr( strlen("STAT:") ), false );
708 	else
709 		requestFile( file, false );
710 	return true;
711 }
712 
requestFileInfo(const std::string & path,bool retryIfFail)713 void CUdpFileDownloader::requestFileInfo( const std::string & path, bool retryIfFail )
714 {
715 	cStatInfo.clear();
716 	setDataToSend( "STAT:", path );
717 	if( retryIfFail )
718 	{
719 		bool exist = false;
720 		for( uint f = 0; f < tRequestedFiles.size(); f++ )
721 			if( tRequestedFiles[f] == "STAT:" + path )
722 				exist = true;
723 		if( ! exist )
724 			tRequestedFiles.push_back( "STAT:" + path );
725 	}
726 	sLastFileRequested = path;
727 }
728 
removeFileFromRequest(const std::string & path)729 void CUdpFileDownloader::removeFileFromRequest( const std::string & path )
730 {
731 	if( sLastFileRequested == path )
732 	{
733 		std::vector< std::string > oldRequestedFiles = tRequestedFiles;
734 		reset();
735 		tRequestedFiles = oldRequestedFiles;
736 	}
737 	for( std::vector< std::string > :: iterator it = tRequestedFiles.begin();
738 			it != tRequestedFiles.end(); it++ )
739 		if( *it == path )
740 		{
741 			tRequestedFiles.erase(it);
742 			return;
743 		}
744 }
745 
abortDownload()746 void CUdpFileDownloader::abortDownload()
747 {
748 	reset();
749 	setDataToSend( "ABORT:", "" );
750 }
751 
752 std::string getStatPacketOneFile( const std::string & path );
753 std::string getStatPacketRecursive( const std::string & path );
754 
processFileRequests()755 void CUdpFileDownloader::processFileRequests()
756 {
757 	// Process received files
758 	for( std::vector< std::string > :: iterator it = tRequestedFiles.begin();
759 			it != tRequestedFiles.end(); )
760 	{
761 		bool erase = false;
762 		if( * it == sFilename )
763 			erase = true;
764 		if( it->find("STAT:") == 0 && sFilename == "STAT_ACK:" )
765 		{
766 			if( getData().find( it->substr(strlen("STAT:")) ) == 0 )
767 				erase = true;
768 		}
769 		if( erase )
770 		{
771 			tRequestedFiles.erase( it );
772 			it = tRequestedFiles.begin();
773 		}
774  		else
775 			it ++ ;
776 	};
777 
778 	// Process file sending requests
779 	if( ! bAllowFileRequest )
780 		return;
781 	if( sFilename == "GET:" )
782 	{
783 		if( ! isPathValid( getData() ) )
784 		{
785 			notes << "CFileDownloaderInGame::processFileRequests(): invalid filename "<< getData() << endl;
786 			return;
787 		};
788 		struct stat st;
789 		if( ! StatFile( getData(), &st ) )
790 		{
791 			notes << "CFileDownloaderInGame::processFileRequests(): cannot stat file " << getData() << endl;
792 			return;
793 		};
794 		if( S_ISREG( st.st_mode ) )
795 		{
796 			setFileToSend( getData() );
797 			return;
798 		};
799 		if( S_ISDIR( st.st_mode ) )
800 		{
801 			notes << "CFileDownloaderInGame::processFileRequests(): cannot send dir (wrong request): " << getData() << endl;
802 			return;
803 		};
804 	};
805 	if( sFilename == "STAT:" )
806 	{
807 		if( ! isPathValid( getData() ) )
808 		{
809 			notes << "CFileDownloaderInGame::processFileRequests(): invalid filename " << getData() << endl;
810 			return;
811 		};
812 		setDataToSend( "STAT_ACK:", getStatPacketRecursive( getData() ) );
813 		return;
814 	};
815 	if( sFilename == "STAT_ACK:" )
816 	{
817 		for( std::string::size_type f = 0, f1 = 0; f < getData().size(); )
818 		{
819 			f1 = getData().find( '\0', f );
820 			if( f1 == f || f1 == std::string::npos || getData().size() < f1 + 13 ) // '\0' + 3 * sizeof(uint)
821 			{
822 				reset();
823 				bWasError = true;
824 				return;
825 			};
826 			std::string filename = getData().substr( f, f1 - f );
827 			f1 ++;
828 			Uint32 size=0, compressedSize=0, checksum=0;
829 			memcpy( &size, getData().data() + f1, 4 );
830 			f1 += 4;
831 			memcpy( &compressedSize, getData().data() + f1, 4 );
832 			f1 += 4;
833 			memcpy( &checksum, getData().data() + f1, 4 );
834 			f1 += 4;
835 			EndianSwap( checksum );
836 			EndianSwap( size );
837 			EndianSwap( compressedSize );
838 			cStatInfo.push_back( StatInfo( filename, size, compressedSize, checksum ) );
839 			f = f1;
840 		};
841 		for( uint ff = 0; ff < cStatInfo.size(); ff++ )
842 		{
843 			cStatInfoCache[ cStatInfo[ff].filename ] = cStatInfo[ff];
844 		};
845 	};
846 	if( sFilename == "ABORT:" )
847 	{
848 		notes << "CFileDownloaderInGame::processFileRequests() - abort received" << endl;
849 		// Server sent us ABORT instead of file - abort only failed request (the last one), leave other requests
850 		if( ! tRequestedFiles.empty() )
851 			tRequestedFiles.pop_back();
852 		iPos = 0;
853 		tPrevState = tState;
854 		tState = S_FINISHED;
855 		sFilename = "";
856 		sData = "";
857 		sLastFileRequested = "";
858 		bWasError = true;
859 		bWasAborted = true;
860 	};
861 };
862 
863 // Valid filename symbols
864 // TODO: don't hardcode this, better check if the path exists/can be created
865 // If path already exists is checked outside of this class,
866 // this func checks if someone tries to access things outside OLX dir or use forbidden symbols for some filesystems.
867 #define S_LETTER_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
868 #define S_LETTER_LOWER "abcdefghijklmnopqrstuvwxyz"
869 #define S_LETTER S_LETTER_UPPER S_LETTER_LOWER
870 #define S_NUMBER "0123456789"
871 #define S_SYMBOL "/. -_=&+!'@$^%()~,[]{}"	// No "\\" symbol, no tab.
872 // Characters 128-255 - valid UTF8 char will consist only of these ones
873 #define S_UTF8_SYMBOL "\128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\224\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\240\241\242\243\244\245\246\247\248\249\250\251\252\253\254\255"
874 const char * invalid_file_names [] = { "CON", "PRN", "AUX", "NUL",
875 	"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
876 	"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
877 
isPathValid(const std::string & path)878 bool CUdpFileDownloader::isPathValid( const std::string & path )
879 {
880 	if( path == "" )
881 		return false;
882 	if( path.find_first_not_of( S_LETTER S_NUMBER S_SYMBOL S_UTF8_SYMBOL ) != std::string::npos )
883 		return false;
884 	if( path[0] == '/' || path[0] == ' ' )
885 		return false;
886 	if( path[path.size()-1] == ' ' )
887 		return false;
888 	if( path.find( ".." ) != std::string::npos ||
889 		path.find( "//" ) != std::string::npos ||
890 		path.find( "./" ) != std::string::npos ||
891 		path.find( "/." ) != std::string::npos )	// Okay, "~/.OpenLieroX/" is valid path, fail it anyway
892 		return false;
893 	if( stringcasefind( path, "cfg/" ) == 0 )	// Config dir is forbidden - some passwords may be stored here
894 		return false;
895 	for( size_t f=0; f < path.size(); )
896 	{
897 		size_t f1 = path.find_first_of(S_SYMBOL, f);
898 		if( f1 == std::string::npos )
899 			f1 = path.size();
900 		std::string word = path.substr( f, f1-f );
901 		for( size_t f2=0; f2<sizeof(invalid_file_names)/sizeof(invalid_file_names[0]); f2++ )
902 		{
903 			if( stringcasecmp( word, invalid_file_names[f2] ) == 0 )
904 				return false;
905 		};
906 		if( f1 == path.size() )
907 			break;
908 		f = path.find_first_not_of(S_SYMBOL, f1);
909 		if( f == std::string::npos )	// Filename cannot end with a dot
910 			return false;
911 	};
912 	return true;
913 };
914 
915 class StatFileList
916 {
917 	public:
918    	std::string *data;
919 	const std::string & reqpath;	// Path as client requested it
920    	int index;
StatFileList(std::string * _data,const std::string & _reqpath)921 	StatFileList( std::string *_data, const std::string & _reqpath ) :
922 		data(_data), reqpath(_reqpath) {}
operator ()(std::string path)923 	bool operator() (std::string path)
924 	{
925 		size_t slash = findLastPathSep(path);
926 		if(slash != std::string::npos)
927 			path.erase(0, slash+1);
928 		*data += getStatPacketOneFile( reqpath + "/" + path );
929 		return true;
930 	};
931 };
932 
933 class StatDirList
934 {
935 	public:
936    	std::string *data;
937 	const std::string & reqpath;	// Path as client requested it
938    	int index;
StatDirList(std::string * _data,const std::string & _reqpath)939 	StatDirList( std::string *_data, const std::string & _reqpath ) :
940 		data(_data), reqpath(_reqpath) {}
operator ()(std::string path)941 	bool operator() (std::string path)
942 	{
943 		size_t slash = findLastPathSep(path);
944 		if(slash != std::string::npos)
945 			path.erase(0, slash+1);
946 		if( path == ".svn" )
947 			return true;
948 		*data += getStatPacketRecursive( reqpath + "/" + path );
949 		return true;
950 	};
951 };
952 
getStatPacketOneFile(const std::string & path)953 std::string getStatPacketOneFile( const std::string & path )
954 {
955 	size_t checksum, size, compressedSize;
956 	if( ! FileChecksum( path, &checksum, &size ) )
957 		return "";
958 	compressedSize = size + path.size() + 24; // Most files from disk are compressed already, so guessing size
959 	EndianSwap( checksum );
960 	EndianSwap( size );
961 	EndianSwap( compressedSize );
962 	return path + '\0' +
963 			std::string( (const char *) (&size), 4 ) + // Real file size
964 			std::string( (const char *) (&compressedSize), 4 ) + // Zipped file size (used for download progressbar, so inexact for now)
965 			std::string( (const char *) (&checksum), 4 );	// Checksum
966 };
967 
getStatPacketRecursive(const std::string & path)968 std::string getStatPacketRecursive( const std::string & path )
969 {
970 		struct stat st;
971 		if( ! StatFile( path, &st ) )
972 		{
973 			notes << "getStatPacketFileOrDir(): cannot stat file " << path << endl;
974 			return "";
975 		};
976 		if( S_ISREG( st.st_mode ) )
977 		{
978 			return getStatPacketOneFile( path );
979 		};
980 		if( S_ISDIR( st.st_mode ) )
981 		{
982 			std::string data;
983 			StatFileList fileWorker( &data, path );
984 			FindFiles( fileWorker, path, false, FM_REG);
985 			StatDirList dirWorker( &data, path );
986 			FindFiles( dirWorker, path, false, FM_DIR);
987 			return data;
988 		};
989 		return "";
990 };
991 
getFileDownloadingProgress() const992 float CUdpFileDownloader::getFileDownloadingProgress() const
993 {
994 	if( getState() != S_RECEIVE )
995 		return 0.0;
996 	if( cStatInfoCache.find(sLastFileRequested) == cStatInfoCache.end() )
997 		return 0.0;
998 	float ret = float(sData.size()) / float(cStatInfoCache.find(sLastFileRequested)->second.compressedSize);
999 	if( ret < 0.0 )
1000 		ret = 0.0;
1001 	if( ret > 1.0 )
1002 		ret = 1.0;
1003 	return ret;
1004 };
1005 
getFileDownloadingProgressBytes() const1006 size_t CUdpFileDownloader::getFileDownloadingProgressBytes() const
1007 {
1008 	if( getState() != S_RECEIVE )
1009 		return 0;
1010 	return sData.size();
1011 };
1012 
getFilesPendingAmount() const1013 size_t CUdpFileDownloader::getFilesPendingAmount() const
1014 {
1015 	return tRequestedFiles.size();
1016 };
1017 
1018 // Calculates the size of all pending files, if there is info asbout them in cache
getFilesPendingSize() const1019 size_t CUdpFileDownloader::getFilesPendingSize() const
1020 {
1021 	size_t sum = 0;
1022 	for( unsigned f = 0; f < tRequestedFiles.size(); f++ )
1023 		if( cStatInfoCache.find(tRequestedFiles[f]) != cStatInfoCache.end() )
1024 			sum += cStatInfoCache.find(tRequestedFiles[f])->second.compressedSize;
1025 	return sum;
1026 };
1027