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