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