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