1 #include <math.h>
2 #include <stdio.h>
3 #include <string>
4 #include <fstream>
5 #include <iostream>
6 #include <sstream>
7 #include <iomanip>
8 #include "Log.h"
9 #include "Networking.h"
10
11 namespace Network
12 {
13
14 /*
15 CCurlTransfer().
16 Constructor.
17 */
CCurlTransfer(const std::string & _name)18 CCurlTransfer::CCurlTransfer( const std::string &_name ) : m_Name( _name ), m_Status( "Idle" ), m_AverageSpeed("0 kb/s")
19 {
20 g_Log->Info( "CCurlTransfer(%s)", _name.c_str() );
21 memset(errorBuffer, 0, CURL_ERROR_SIZE);
22 m_pCurl = curl_easy_init();
23 if( !m_pCurl )
24 g_Log->Info( "Failed to init curl instance." );
25
26 m_pCurlM = curl_multi_init();
27 if( !m_pCurlM )
28 g_Log->Info( "Failed to init curl multi instance." );
29
30 if ( m_pCurl != NULL && m_pCurlM != NULL )
31 curl_multi_add_handle( m_pCurlM, m_pCurl );
32 }
33
34 /*
35 ~CCurlTransfer().
36 Destructor.
37 */
~CCurlTransfer()38 CCurlTransfer::~CCurlTransfer()
39 {
40 //g_NetworkManager->Remove( this );
41 g_Log->Info( "~CCurlTransfer()" );
42
43 if ( m_pCurlM != NULL && m_pCurl != NULL )
44 curl_multi_remove_handle(m_pCurlM, m_pCurl);
45
46 if( m_pCurl != NULL )
47 {
48 curl_easy_cleanup( m_pCurl );
49 m_pCurl = NULL;
50 }
51
52 if ( m_pCurlM != NULL )
53 {
54 curl_multi_cleanup( m_pCurlM );
55 m_pCurlM = NULL;
56 }
57 }
58
59 /*
60 Verify.
61 User throughout these classes to verify curl integrity.
62 */
Verify(CURLcode _code)63 bool CCurlTransfer::Verify( CURLcode _code )
64 {
65 g_Log->Info( "Verify(%d)", _code );
66
67 if( _code != CURLE_OK )
68 {
69 Status( curl_easy_strerror( _code ) );
70 return false;
71 }
72
73 return true;
74 }
75
76 /*
77 VerifyM.
78 User throughout these classes to verify curl multi integrity.
79 */
VerifyM(CURLMcode _code)80 bool CCurlTransfer::VerifyM( CURLMcode _code )
81 {
82 g_Log->Info( "VerifyM(%d)", _code );
83
84 if( _code != CURLM_OK && _code != CURLM_CALL_MULTI_PERFORM)
85 {
86 Status( curl_multi_strerror( _code ) );
87 return false;
88 }
89
90 return true;
91 }
92
93
94 /*
95 */
customProgressCallback(void * _pUserData,fp8 _downTotal,fp8 _downNow,fp8 _upTotal,fp8 _upNow)96 int CCurlTransfer::customProgressCallback( void *_pUserData, fp8 _downTotal, fp8 _downNow, fp8 _upTotal, fp8 _upNow )
97 {
98 //g_Log->Info( "customProgressCallback()" );
99
100 if (!g_NetworkManager->SingletonActive() || g_NetworkManager->IsAborted())
101 return -1;
102
103 CCurlTransfer *pOut = static_cast<CCurlTransfer *>(_pUserData);
104 if( !pOut )
105 {
106 g_Log->Info( "Error, no _pUserData" );
107 return -1;
108 }
109
110 if (g_NetworkManager)
111 g_NetworkManager->UpdateProgress( pOut, ((_downTotal+_upTotal) > 0) ? ((_downNow+_upNow) / (_downTotal+_upTotal) * 100) : 0, _downNow + _upNow );
112 return 0;
113 }
114
115 /*
116 Proxy().
117 Set proxy info used during transfer.
118 */
Proxy(const std::string & _url,const std::string & _userName,const std::string & _password)119 void CManager::Proxy( const std::string &_url, const std::string &_userName, const std::string &_password )
120 {
121 g_Log->Info( "Proxy()" );
122
123 if( _url == "" )
124 return;
125
126 boost::mutex::scoped_lock locker( m_Lock );
127
128 // Set proxy url now, which will allow a non-user/pass proxy to be used, as reported on the forum.
129 m_ProxyUrl = _url;
130
131 if( _userName == "" || _password == "" )
132 return;
133
134 std::stringstream pu;
135 pu << _userName << ":" << _password;
136 m_ProxyUserPass = pu.str();
137 }
138
139 /*
140 Login().
141 Set authentication user/pass for transfer. Default method is basic.
142 */
Login(const std::string & _userName,const std::string & _password)143 void CManager::Login( const std::string &_userName, const std::string &_password )
144 {
145 g_Log->Info( "Login()" );
146
147 if( _userName == "" || _password == "" )
148 return;
149
150 boost::mutex::scoped_lock locker( m_Lock );
151
152 std::stringstream pu;
153 pu << _userName << ":" << _password;
154 m_UserPass = pu.str();
155 }
156
157 /*
158 Logout().
159 Clears authentication user/pass.
160 */
Logout()161 void CManager::Logout()
162 {
163 g_Log->Info( "Logout()" );
164 boost::mutex::scoped_lock locker( m_Lock );
165 m_UserPass = "";
166 }
167
InterruptiblePerform()168 bool CCurlTransfer::InterruptiblePerform()
169 {
170 CURLMcode _code;
171 int running_handles, running_handles_last;
172 fd_set fd_read, fd_write, fd_except;
173 int max_fd;
174 long timeout;
175 struct timeval tval, orig_tval;
176
177 _code = curl_multi_perform( m_pCurlM, &running_handles );
178
179 if ( !VerifyM(_code) )
180 return false;
181
182 if ( running_handles == 0 )
183 return true;
184
185 running_handles_last = running_handles;
186 _code = CURLM_CALL_MULTI_PERFORM;
187
188 while( 1 )
189 {
190 while ( _code == CURLM_CALL_MULTI_PERFORM )
191 {
192 if (!g_NetworkManager->SingletonActive() || g_NetworkManager->IsAborted())
193 return false;
194
195 _code = curl_multi_perform (m_pCurlM, &running_handles );
196 }
197
198 if ( !VerifyM( _code ) )
199 return false;
200
201 if ( running_handles < running_handles_last )
202 break;
203
204 FD_ZERO( &fd_read );
205 FD_ZERO( &fd_write );
206 FD_ZERO( &fd_except );
207
208 _code = curl_multi_fdset( m_pCurlM, &fd_read, &fd_write, &fd_except, &max_fd );
209
210 if ( !VerifyM( _code ) )
211 return false;
212
213 if (-1 == max_fd)
214 {
215 _code = CURLM_CALL_MULTI_PERFORM;
216 continue;
217 }
218
219 timeout = -1;
220
221 #ifdef CURL_MULTI_TIMEOUT
222 _code = curl_multi_timeout( m_pCurlM, &timeout );
223
224 if ( !VerifyM( _code ) )
225 return false;
226 #endif
227
228 if (timeout == -1)
229 timeout = 100;
230
231 tval.tv_sec = timeout / 1000;
232 tval.tv_usec = timeout % 1000 * 1000;
233
234 orig_tval = tval;
235
236 int err;
237
238 if (!g_NetworkManager->SingletonActive() || g_NetworkManager->IsAborted())
239 return false;
240
241 while ( ( err = select( max_fd + 1, &fd_read, &fd_write, &fd_except, &tval ) ) < 0 )
242 {
243 #ifndef WIN32
244 if ( errno != EINTR )
245 {
246 return false;
247 }
248 #endif
249 if (!g_NetworkManager->SingletonActive() || g_NetworkManager->IsAborted())
250 return false;
251
252 //tval should be considered invalid after "select" returns
253 tval = orig_tval;
254 }
255
256 _code = CURLM_CALL_MULTI_PERFORM;
257 }
258
259 return true;
260 }
261
262
263 /*
264 Perform().
265 Do the actual transfer.
266 */
Perform(const std::string & _url)267 bool CCurlTransfer::Perform( const std::string &_url )
268 {
269 if (!g_NetworkManager->SingletonActive() || g_NetworkManager->IsAborted())
270 return false;
271
272 std::string url = _url;
273
274 g_Log->Info( "Perform(%s)", url.c_str() );
275 if( !m_pCurl )
276 return false;
277
278
279 g_Log->Info( "0x%x", m_pCurl );
280
281 #ifdef DEBUG
282 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_VERBOSE, 1 ) ) ) return false;
283 #endif
284
285 // Ask manager to prepare this transfer for us.
286 if( !Verify( g_NetworkManager->Prepare( m_pCurl ) ) ) return false;
287
288 g_Log->Info( "Performing '%s'", url.c_str() );
289 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_URL, url.c_str() ) ) ) return false;
290
291 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_NOPROGRESS, 0 ) ) ) return false;
292 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_PROGRESSFUNCTION, &CCurlTransfer::customProgressCallback ) ) ) return false;
293 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_PROGRESSDATA, this ) ) ) return false;
294 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_ERRORBUFFER, errorBuffer ) ) ) return false;
295
296 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_FOLLOWLOCATION, 1 ) ) ) return false;
297 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_MAXREDIRS, 5 ) ) ) return false;
298
299 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_SSL_VERIFYHOST, 0 ) ) ) return false;
300 if( !Verify( curl_easy_setopt( m_pCurl, CURLOPT_SSL_VERIFYPEER, 0 ) ) ) return false;
301
302 Status( "Active" );
303
304 //if( !Verify( curl_easy_perform( m_pCurl ) ) )
305 if ( !InterruptiblePerform() )
306 {
307 g_Log->Warning( errorBuffer );
308 Status( "Failed" );
309 return false;
310 }
311 else
312 Status( "Completed" );
313
314 // Need to do this to trigger the strings to propagate.
315 g_NetworkManager->UpdateProgress( this, 100, 0 );
316
317 if( !Verify( curl_easy_getinfo( m_pCurl, CURLINFO_RESPONSE_CODE, &m_HttpCode ) ) ) return false;
318 if( m_HttpCode != 200 )
319 {
320 // Check if the response code is allowed.
321 std::vector< uint32 >::const_iterator it = std::find( m_AllowedResponses.begin(), m_AllowedResponses.end(), m_HttpCode );
322 if( it == m_AllowedResponses.end() )
323 {
324 switch ( m_HttpCode )
325 {
326 case 500:
327 Status("Internal Server Error\n");
328 break;
329 case 401:
330 Status( "Authentication failed\n" );
331 break;
332
333 case 404:
334 Status( "File not found on server\n" );
335 break;
336
337 default:
338 {
339 std::stringstream st;
340 st << "Invalid server response [" << m_HttpCode << "]\n";
341
342 Status( st.str() );
343 }
344 break;
345 }
346
347 // Todo, (or not) print the remaining ones :)
348 return false;
349 }
350 }
351
352 fp8 speedUp = 0;
353 fp8 speedDown = 0;
354 if( !Verify( curl_easy_getinfo( m_pCurl, CURLINFO_SPEED_UPLOAD, &speedDown ) ) ) return false;
355 if( !Verify( curl_easy_getinfo( m_pCurl, CURLINFO_SPEED_DOWNLOAD, &speedUp ) ) ) return false;
356
357 std::stringstream statusres;
358 statusres << "~" << (uint32)((speedUp+speedDown)/1000) << " kb/s";
359 m_AverageSpeed = statusres.str();
360
361 g_Log->Info( "Perform() complete" );
362
363 return true;
364 }
365
366 /*
367 CManager().
368 Constructor.
369 */
CManager()370 CManager::CManager()
371 {
372 }
373
374 /*
375 Startup().
376 Init network manager.
377 */
Startup()378 bool CManager::Startup()
379 {
380 curl_global_init( CURL_GLOBAL_DEFAULT );
381
382 m_UserPass = "";
383 m_ProxyUrl = "";
384 m_ProxyUserPass = "";
385
386 m_Aborted = false;
387
388 return true;
389 }
390
391 /*
392 Startup().
393 De-init network manager.
394 */
Shutdown()395 bool CManager::Shutdown()
396 {
397 curl_global_cleanup();
398 return true;
399 }
400
401 /*
402 Prepare()-
403 Called from CCurlTransfer::Perform().
404 Sets proxy & http authentication.
405 */
Prepare(CURL * _pCurl)406 CURLcode CManager::Prepare( CURL *_pCurl )
407 {
408 g_Log->Info( "Prepare()" );
409
410 boost::mutex::scoped_lock locker( m_Lock );
411
412 CURLcode code = CURLE_OK;
413
414 // Set http authentication if there is one.
415 if( m_UserPass != "" )
416 {
417 curl_easy_setopt(_pCurl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
418
419 code = curl_easy_setopt( _pCurl, CURLOPT_USERPWD, m_UserPass.c_str() );
420
421 if( code != CURLE_OK )
422 return code;
423 }
424
425 // Set proxy url and user/pass if they're defined.
426 if( m_ProxyUrl != "" )
427 {
428 code = curl_easy_setopt( _pCurl, CURLOPT_PROXY, m_ProxyUrl.c_str() );
429 if( code != CURLE_OK ) return code;
430
431 if( m_ProxyUserPass != "" )
432 code = curl_easy_setopt( _pCurl, CURLOPT_PROXYUSERPWD, m_ProxyUserPass.c_str() );
433 }
434
435 return code;
436 }
437
438 /*
439 Abort().
440 Sets the abort flags then used by any transfer to abort itself.
441 */
Abort(void)442 void CManager::Abort( void )
443 {
444 boost::mutex::scoped_lock locker( m_Lock );
445
446 m_Aborted = true;
447 }
448
449 /*
450 Abort().
451 Sets the abort flags then used by any transfer to abort itself.
452 */
IsAborted(void)453 bool CManager::IsAborted( void )
454 {
455 boost::mutex::scoped_lock locker( m_Lock );
456
457 return m_Aborted;
458 }
459
460
461 /*
462 UpdateProgress().
463 Called from the callback of each CCurlTransfer instance to update progress.
464 */
UpdateProgress(CCurlTransfer * _pTransfer,const fp8 _percentComplete,const fp8 _bytesTransferred)465 void CManager::UpdateProgress( CCurlTransfer *_pTransfer, const fp8 _percentComplete, const fp8 _bytesTransferred )
466 {
467 boost::mutex::scoped_lock locker( m_Lock );
468
469 //g_Log->Info( "UpdateProgress()" );
470
471 if( !_pTransfer )
472 return;
473
474 std::stringstream tmp;
475 tmp << _pTransfer->Name() << " (" << _pTransfer->Status() << ")";
476 if( _pTransfer->Status() == "Active" )
477 {
478 tmp << ": " << (int32)_percentComplete << "%";
479 if (_bytesTransferred > 1024 * 1024)
480 tmp << std::fixed << std::setprecision(1) << " (" << (_bytesTransferred/(1024.0 * 1024)) << " MB)";
481 else if (_bytesTransferred > 1024)
482 tmp << std::fixed << std::setprecision(0) << " (" << (_bytesTransferred/(1024.0)) << " kB)";
483 else
484 tmp << std::fixed << std::setprecision(0) << " (" << _bytesTransferred << " B)";
485 }
486
487 //g_Log->Info( "Setting progress!" );
488 m_ProgressMap[ _pTransfer->Name() ] = tmp.str();
489 }
490
491 /*
492 Status().
493 Creates a string with status for all active transfers.
494 */
Status()495 std::string CManager::Status()
496 {
497 boost::mutex::scoped_lock locker( m_Lock );
498
499 std::string res = "";
500
501 if( m_ProgressMap.size() == 0 )
502 return res;
503
504 std::map<std::string, std::string>::iterator iter;
505 for( iter=m_ProgressMap.begin(); iter != m_ProgressMap.end(); )
506 {
507 std::string s = iter->second;
508
509 std::string::size_type loc = s.find( "Completed", 0 );
510 if( loc != std::string::npos )
511 {
512 std::map<std::string, std::string>::iterator next = iter;
513 ++next;
514 m_ProgressMap.erase( iter );
515 iter = next;
516 }
517 else
518 {
519 res += s + "\n";
520 ++iter;
521 }
522 }
523
524 if (res.size() > 0)
525 res.erase(res.size()-1);
526 return res;
527 }
528
529 /*
530 Remove().
531 Removes transfer from progressmap.
532 */
Remove(CCurlTransfer * _pTransfer)533 void CManager::Remove( CCurlTransfer *_pTransfer )
534 {
535 g_Log->Info( "Remove()" );
536 boost::mutex::scoped_lock locker( m_Lock );
537 m_ProgressMap.erase( _pTransfer->Name() );
538 }
539
540
541 /*
542 Encode().
543 Url encode a string, returns a new string.
544 */
Encode(const std::string & _src)545 std::string CManager::Encode( const std::string &_src )
546 {
547 g_Log->Info( "Encode()" );
548
549 const uint8 dec2hex[ 16 + 1 ] = "0123456789ABCDEF";
550 const uint8 *pSrc = (const uint8 *)_src.c_str();
551 const size_t srcLen= _src.length();
552 uint8 *const pStart = new uint8[ srcLen * 3 ];
553 uint8 *pEnd = pStart;
554 const uint8 * const srcEnd = pSrc + srcLen;
555
556 for( ; pSrc<srcEnd; ++pSrc )
557 {
558 if( isalnum( *pSrc ) )
559 *pEnd++ = *pSrc;
560 else
561 {
562 // Escape this char.
563 *pEnd++ = '%';
564 *pEnd++ = dec2hex[ *pSrc >> 4 ];
565 *pEnd++ = dec2hex[ *pSrc & 0x0F ];
566 }
567 }
568
569 std::string sResult( (char *)pStart, (char *)pEnd );
570 delete [] pStart;
571 return sResult;
572 }
573
574 };
575