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 // HTTP class implementation
16 // Created 9/4/02
17 // Jason Boettcher
18 
19 
20 #include <cassert>
21 #ifdef WIN32
22 	#include <windows.h>
23 	#include <wininet.h>
24 #else
25 	#include <stdlib.h>
26 #endif
27 #include <curl/curl.h>
28 #include <curl/easy.h>
29 
30 #include "LieroX.h"
31 #include "Debug.h"
32 #include "Options.h"
33 #include "HTTP.h"
34 #include "Timer.h"
35 #include "StringUtils.h"
36 #include "types.h"
37 #include "Version.h"
38 #include "MathLib.h"
39 #include "InputEvents.h"
40 #include "ReadWriteLock.h"
41 
42 // Some basic defines
43 #define		HTTP_TIMEOUT	10	// Filebase became laggy lately, so increased that from 5 seconds
44 //#define		BUFFER_LEN		8192
45 
46 
47 //
48 // Functions
49 //
50 
51 /////////////////
52 // Automatically updates tLXOptions->sHttpProxy with proxy settings retrieved from the system
AutoSetupHTTPProxy()53 void AutoSetupHTTPProxy()
54 {
55 	// User doesn't wish an automatic proxy setup
56 	if (!tLXOptions->bAutoSetupHttpProxy) {
57 		notes << "AutoSetupHTTPProxy is disabled, ";
58 		if(tLXOptions->sHttpProxy != "")
59 			notes << "using proxy " << tLXOptions->sHttpProxy << endl;
60 		else
61 			notes << "not using any proxy" << endl;
62 		return;
63 	}
64 
65 	if(tLXOptions->sHttpProxy != "") {
66 		notes << "AutoSetupHTTPProxy: we had the proxy " << tLXOptions->sHttpProxy << " but we are trying to autodetect it now" << endl;
67 	}
68 
69 // DevCpp won't compile this - too old header files
70 #if defined( WIN32 ) && defined( MSC_VER )
71 	// Create the list of options we want to retrieve
72 	INTERNET_PER_CONN_OPTION_LIST List;
73 	INTERNET_PER_CONN_OPTION Options[2];
74 	DWORD size = sizeof(INTERNET_PER_CONN_OPTION_LIST);
75 
76 	// Options we need
77 	Options[0].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
78 	Options[1].dwOption = INTERNET_PER_CONN_FLAGS;
79 
80 	// Fill the list info
81 	List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
82 	List.pszConnection = NULL;
83 	List.dwOptionCount = 2;
84 	List.dwOptionError = 0;
85 	List.pOptions = Options;
86 
87 	// Ask for proxy info
88 	if(InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &size)) {
89 
90 		// Using proxy?
91 		bool using_proxy = (Options[1].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY;
92 
93 		if (using_proxy)  {
94 			if (Options[0].Value.pszValue != NULL)  { // Safety check
95 				tLXOptions->sHttpProxy = Options[0].Value.pszValue; // Set the proxy
96 
97 				// Remove http:// if present
98 				static const size_t httplen = 7; // length of "http://"
99 				if( stringcaseequal(tLXOptions->sHttpProxy.substr(0, httplen), "http://") )
100 					tLXOptions->sHttpProxy.erase(0, httplen);
101 
102 				// Remove trailing slash
103 				if (*tLXOptions->sHttpProxy.rbegin() == '/')
104 					tLXOptions->sHttpProxy.resize(tLXOptions->sHttpProxy.size() - 1);
105 
106 				notes << "Using HTTP proxy: " << tLXOptions->sHttpProxy << endl;
107 			}
108 		} else {
109 			tLXOptions->sHttpProxy = ""; // No proxy
110 		}
111 
112 		// Cleanup
113 		if(Options[0].Value.pszValue != NULL)
114 			GlobalFree(Options[0].Value.pszValue);
115 	}
116 
117 #else
118 	// Linux has numerous configuration of proxies for each application, but environment var seems to be the most common
119 	const char * c_proxy = getenv("http_proxy");
120 	if( c_proxy == NULL )  {
121 		c_proxy = getenv("HTTP_PROXY");
122 	}
123 
124 	if(c_proxy) {
125 		// Get the value (string after '=' char)
126 		std::string proxy(c_proxy);
127 		if( proxy.find('=') == std::string::npos )  { // No proxy
128 			tLXOptions->sHttpProxy = "";
129 			return;
130 		}
131 		proxy = proxy.substr( proxy.find('=') + 1 );
132 		TrimSpaces(proxy);
133 
134 		// Remove http:// if present
135 		static const size_t httplen = 7; // length of "http://"
136 		if( stringcaseequal(proxy.substr(0, httplen), "http://") )
137 			proxy.erase(0, httplen);
138 
139 		// Blank proxy?
140 		if( proxy != "" )  {
141 			// Remove trailing slash
142 			if( *proxy.rbegin() == '/')
143 				proxy.resize( proxy.size() - 1 );
144 		}
145 
146 		tLXOptions->sHttpProxy = proxy;
147 
148 		notes << "AutoSetupHTTPProxy: " << proxy << endl;
149 	}
150 #endif
151 }
152 
153 
154 
155 struct CurlThread : Action {
156 
CurlThreadCurlThread157 	CurlThread( CHttp * parent, CURL * _curl ) :
158 		parent( parent ),
159 		curl( _curl ),
160 		curlForm( NULL )
161 	{
162 		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlReceiveCallback);
163 		curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)this);
164 		//curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
165 		//curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, CurlProgressCallback);
166 		//curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, (void *)this);
167 	}
168 
169 	int handle();
170 
171 	CHttp *			parent;
172 	CURL *			curl;
173 	curl_httppost *	curlForm;
174 	Mutex			Lock;
175 
176 	static size_t CurlReceiveCallback(void *ptr, size_t size, size_t nmemb, void *data);
177 	//static int CurlProgressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
178 };
179 
CHttp()180 CHttp::CHttp()
181 {
182 	ProcessingResult = HTTP_PROC_FINISHED;
183 	DownloadStart = DownloadEnd = 0;
184 	curlThread = NULL;
185 }
186 
~CHttp()187 CHttp::~CHttp()
188 {
189 	CancelProcessing();
190 }
191 
CurlReceiveCallback(void * ptr,size_t size,size_t nmemb,void * data)192 size_t CurlThread::CurlReceiveCallback(void *ptr, size_t size, size_t nmemb, void *data)
193 {
194 	CurlThread* self = (CurlThread *)data;
195 	size_t realsize = size * nmemb;
196 
197 	Mutex::ScopedLock l(self->Lock);
198 
199 	if( !self->parent ) // Aborting
200 		return 0;
201 
202 	Mutex::ScopedLock l1(self->parent->Lock);
203 	self->parent->Data.append((const char *)ptr, realsize);
204 
205 	return realsize;
206 }
207 
208 /*
209 int	CurlThread::CurlProgressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
210 	CurlThread* self = (CurlThread *)clientp;
211 
212 	Mutex::ScopedLock l(self->Lock);
213 	if( !self->parent ) // Aborting
214 		return 0;
215 
216 	return 1;
217 }
218 */
219 
InitializeTransfer(const std::string & url,const std::string & proxy)220 CURL * CHttp::InitializeTransfer(const std::string& url, const std::string& proxy)
221 {
222 	CancelProcessing();
223 	ProcessingResult = HTTP_PROC_PROCESSING;
224 	Url = url;
225 	Proxy = proxy;
226 	Useragent = GetFullGameName();
227 	Data = "";
228 	DownloadStart = DownloadEnd = tLX->currentTime;
229 
230 	CURL * curl = curl_easy_init();
231 	curl_easy_setopt( curl, CURLOPT_URL, Url.c_str() );
232 	curl_easy_setopt( curl, CURLOPT_PROXY, Proxy.c_str() );
233 	curl_easy_setopt( curl, CURLOPT_USERAGENT, Useragent.c_str() );
234 	curl_easy_setopt( curl, CURLOPT_NOSIGNAL, (long) 1 );
235 	curl_easy_setopt( curl, CURLOPT_CONNECTTIMEOUT, (long) HTTP_TIMEOUT );
236 	curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, (long) 1 ); // Allow server to use 3XX Redirect codes
237 	curl_easy_setopt( curl, CURLOPT_MAXREDIRS, (long) 25 ); // Some reasonable limit
238 	curl_easy_setopt( curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); // Force IPv4
239 	//curl_easy_setopt( curl, CURLOPT_TIMEOUT, (long) HTTP_TIMEOUT ); // Do not set this if you don't want abort in the middle of large transfer
240 	return curl;
241 }
242 
RequestData(const std::string & url,const std::string & proxy)243 void CHttp::RequestData(const std::string& url, const std::string& proxy)
244 {
245 	CURL * curl = InitializeTransfer(url, proxy);
246 	curlThread = new CurlThread(this, curl);
247 	threadPool->start(curlThread, "CHttp: " + Url, true);
248 }
249 
SendData(const std::list<HTTPPostField> & data,const std::string url,const std::string & proxy)250 void CHttp::SendData(const std::list<HTTPPostField>& data, const std::string url, const std::string& proxy)
251 {
252 	CURL * curl = InitializeTransfer(url, proxy);
253 
254 	curl_httppost * curlForm = NULL;
255 	struct curl_httppost *lastptr = NULL;
256 
257 	for( std::list<HTTPPostField> :: const_iterator it = data.begin(); it != data.end(); it++ )
258 	{
259 		if( it->getFileName() == "" )
260 			curl_formadd(	&curlForm,
261 							&lastptr,
262 							CURLFORM_COPYNAME, it->getName().c_str(),
263 							CURLFORM_CONTENTSLENGTH, it->getData().size(),
264 							CURLFORM_COPYCONTENTS, it->getData().c_str(),
265 							CURLFORM_END);
266 		else
267 			curl_formadd(	&curlForm,
268 							&lastptr,
269 							CURLFORM_COPYNAME, it->getName().c_str(),
270 							CURLFORM_FILENAME, it->getFileName().c_str(),
271 							CURLFORM_CONTENTTYPE, it->getMimeType().c_str(),
272 							CURLFORM_CONTENTSLENGTH, it->getData().size(),
273 							CURLFORM_COPYCONTENTS, it->getData().c_str(),
274 							CURLFORM_END);
275 	}
276 
277 	curl_easy_setopt(curl, CURLOPT_HTTPPOST, curlForm);
278 	curlThread = new CurlThread(this, curl);
279 	curlThread->curlForm = curlForm;
280 	threadPool->start(curlThread, "CHttp: " + Url, true);
281 }
282 
handle()283 int CurlThread::handle()
284 {
285 	CURLcode res = curl_easy_perform(curl); // Blocks until processing finished
286 
287 	Mutex::ScopedLock l(Lock);
288 	if( parent != NULL )
289 	{
290 		Mutex::ScopedLock l1(parent->Lock);
291 
292 		parent->curlThread = NULL;
293 
294 		parent->ProcessingResult = HTTP_PROC_FINISHED;
295 		parent->Error.iError = HTTP_NO_ERROR;
296 		if( res != CURLE_OK )
297 		{
298 			parent->ProcessingResult = HTTP_PROC_ERROR;
299 			parent->Error.iError = res; // This is not HTTP error, it's libcurl error, but whatever
300 			parent->Error.sErrorMsg = curl_easy_strerror(res);
301 		}
302 		parent->DownloadEnd = tLX->currentTime;
303 
304 		if( curlForm != NULL )
305 			curl_formfree(curlForm);
306 		curlForm = NULL;
307 
308 		parent->onFinished.occurred(CHttp::HttpEventData(parent, parent->ProcessingResult == HTTP_PROC_FINISHED));
309 		parent = NULL;
310 	}
311 
312 	curl_easy_cleanup(curl);
313 
314 	return 0;
315 }
316 
CancelProcessing()317 void CHttp::CancelProcessing() // Non-blocking
318 {
319 	Mutex::ScopedLock l(Lock);
320 
321 	if(curlThread != NULL)
322 	{
323 		Mutex::ScopedLock l(curlThread->Lock);
324 		curlThread->parent = NULL;
325 		curlThread = NULL;
326 	}
327 }
328 
GetDataLength() const329 size_t CHttp::GetDataLength() const
330 {
331 	Mutex::ScopedLock l(const_cast<Mutex &>(Lock));
332 	return Data.size();
333 }
334 
GetMimeType() const335 std::string CHttp::GetMimeType() const
336 {
337 	Mutex::ScopedLock l(const_cast<Mutex &>(Lock));
338 	if( !curlThread )
339 		return "";
340 	Mutex::ScopedLock l1(curlThread->Lock);
341 	const char * c = NULL;
342 	curl_easy_getinfo(curlThread->curl, CURLINFO_CONTENT_TYPE, c);
343 	std::string ret;
344 	if( c != NULL )
345 		ret = c;
346 	return ret;
347 }
348 
GetDownloadSpeed() const349 float CHttp::GetDownloadSpeed() const
350 {
351 	Mutex::ScopedLock l(const_cast<Mutex &>(Lock));
352 	if( !curlThread )
353 		return 0;
354 	Mutex::ScopedLock l1(curlThread->Lock);
355 	double d = 0;
356 	curl_easy_getinfo(curlThread->curl, CURLINFO_SPEED_DOWNLOAD, &d);
357 	return float(d);
358 }
359 
GetUploadSpeed() const360 float CHttp::GetUploadSpeed() const
361 {
362 	Mutex::ScopedLock l(const_cast<Mutex &>(Lock));
363 	double d = 0;
364 	if( !curlThread )
365 		return 0;
366 	Mutex::ScopedLock l1(curlThread->Lock);
367 	curl_easy_getinfo(curlThread->curl, CURLINFO_SPEED_UPLOAD, &d);
368 	return float(d);
369 }
370 
GetHostName() const371 std::string CHttp::GetHostName() const
372 {
373 	std::string sHost;
374 	size_t s = 0;
375 	if( Url.find("http://") != std::string::npos )
376 		s = Url.find("http://") + 7;
377 
378 	size_t p = Url.find("/", s );
379 	if(p == std::string::npos)
380 		sHost = Url.substr(s);
381 	else
382 		sHost = Url.substr(s, p-s);
383 
384 	return sHost;
385 }
386 
GetDataToSendLength() const387 size_t CHttp::GetDataToSendLength() const
388 {
389 	Mutex::ScopedLock l(const_cast<Mutex &>(Lock));
390 	double d = 0;
391 	if( !curlThread )
392 		return 0;
393 	Mutex::ScopedLock l1(curlThread->Lock);
394 	curl_easy_getinfo(curlThread->curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &d);
395 	if( d <= 0 )
396 		d = 1;
397 	return (size_t)d;
398 };
399 
GetSentDataLen() const400 size_t CHttp::GetSentDataLen() const
401 {
402 	Mutex::ScopedLock l(const_cast<Mutex &>(Lock));
403 	double d = 0, total = 0;
404 	if( !curlThread )
405 		return 0;
406 	Mutex::ScopedLock l1(curlThread->Lock);
407 	curl_easy_getinfo(curlThread->curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &total);
408 	curl_easy_getinfo(curlThread->curl, CURLINFO_SIZE_UPLOAD, &d);
409 	if( d > total )
410 		d = total;
411 	if( d <= 0 )
412 		d = 1;
413 	return (size_t)d;
414 };
415