1 /*
2 ===========================================================================
3 Copyright (C) 2006 Tony J. White (tjw@tjw.org)
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22
23 #ifdef USE_CURL
24 #include "client.h"
25 cvar_t *cl_cURLLib;
26
27 #ifdef USE_CURL_DLOPEN
28 #include "../sys/sys_loadlib.h"
29
30 char* (*qcurl_version)(void);
31
32 CURL* (*qcurl_easy_init)(void);
33 CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
34 CURLcode (*qcurl_easy_perform)(CURL *curl);
35 void (*qcurl_easy_cleanup)(CURL *curl);
36 CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
37 CURL* (*qcurl_easy_duphandle)(CURL *curl);
38 void (*qcurl_easy_reset)(CURL *curl);
39 const char *(*qcurl_easy_strerror)(CURLcode);
40
41 CURLM* (*qcurl_multi_init)(void);
42 CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle,
43 CURL *curl_handle);
44 CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle,
45 CURL *curl_handle);
46 CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle,
47 fd_set *read_fd_set,
48 fd_set *write_fd_set,
49 fd_set *exc_fd_set,
50 int *max_fd);
51 CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle,
52 int *running_handles);
53 CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
54 CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle,
55 int *msgs_in_queue);
56 const char *(*qcurl_multi_strerror)(CURLMcode);
57
58 static void *cURLLib = NULL;
59
60 /*
61 =================
62 GPA
63 =================
64 */
GPA(char * str)65 static void *GPA(char *str)
66 {
67 void *rv;
68
69 rv = Sys_LoadFunction(cURLLib, str);
70 if(!rv)
71 {
72 Com_Printf("Can't load symbol %s\n", str);
73 clc.cURLEnabled = qfalse;
74 return NULL;
75 }
76 else
77 {
78 Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv);
79 return rv;
80 }
81 }
82 #endif /* USE_CURL_DLOPEN */
83
84 /*
85 =================
86 CL_cURL_Init
87 =================
88 */
CL_cURL_Init()89 qboolean CL_cURL_Init()
90 {
91 #ifdef USE_CURL_DLOPEN
92 if(cURLLib)
93 return qtrue;
94
95
96 Com_Printf("Loading \"%s\"...", cl_cURLLib->string);
97 if( (cURLLib = Sys_LoadLibrary(cl_cURLLib->string)) == 0 )
98 {
99 #ifdef _WIN32
100 return qfalse;
101 #else
102 char fn[1024];
103
104 Q_strncpyz( fn, Sys_Cwd( ), sizeof( fn ) );
105 strncat(fn, "/", sizeof(fn)-strlen(fn)-1);
106 strncat(fn, cl_cURLLib->string, sizeof(fn)-strlen(fn)-1);
107
108 if((cURLLib = Sys_LoadLibrary(fn)) == 0)
109 {
110 #ifdef ALTERNATE_CURL_LIB
111 // On some linux distributions there is no libcurl.so.3, but only libcurl.so.4. That one works too.
112 if( (cURLLib = Sys_LoadLibrary(ALTERNATE_CURL_LIB)) == 0 )
113 {
114 return qfalse;
115 }
116 #else
117 return qfalse;
118 #endif
119 }
120 #endif /* _WIN32 */
121 }
122
123 clc.cURLEnabled = qtrue;
124
125 qcurl_version = GPA("curl_version");
126
127 qcurl_easy_init = GPA("curl_easy_init");
128 qcurl_easy_setopt = GPA("curl_easy_setopt");
129 qcurl_easy_perform = GPA("curl_easy_perform");
130 qcurl_easy_cleanup = GPA("curl_easy_cleanup");
131 qcurl_easy_getinfo = GPA("curl_easy_getinfo");
132 qcurl_easy_duphandle = GPA("curl_easy_duphandle");
133 qcurl_easy_reset = GPA("curl_easy_reset");
134 qcurl_easy_strerror = GPA("curl_easy_strerror");
135
136 qcurl_multi_init = GPA("curl_multi_init");
137 qcurl_multi_add_handle = GPA("curl_multi_add_handle");
138 qcurl_multi_remove_handle = GPA("curl_multi_remove_handle");
139 qcurl_multi_fdset = GPA("curl_multi_fdset");
140 qcurl_multi_perform = GPA("curl_multi_perform");
141 qcurl_multi_cleanup = GPA("curl_multi_cleanup");
142 qcurl_multi_info_read = GPA("curl_multi_info_read");
143 qcurl_multi_strerror = GPA("curl_multi_strerror");
144
145 if(!clc.cURLEnabled)
146 {
147 CL_cURL_Shutdown();
148 Com_Printf("FAIL One or more symbols not found\n");
149 return qfalse;
150 }
151 Com_Printf("OK\n");
152
153 return qtrue;
154 #else
155 clc.cURLEnabled = qtrue;
156 return qtrue;
157 #endif /* USE_CURL_DLOPEN */
158 }
159
160 /*
161 =================
162 CL_cURL_Shutdown
163 =================
164 */
CL_cURL_Shutdown(void)165 void CL_cURL_Shutdown( void )
166 {
167 CL_cURL_Cleanup();
168 #ifdef USE_CURL_DLOPEN
169 if(cURLLib)
170 {
171 Sys_UnloadLibrary(cURLLib);
172 cURLLib = NULL;
173 }
174 qcurl_easy_init = NULL;
175 qcurl_easy_setopt = NULL;
176 qcurl_easy_perform = NULL;
177 qcurl_easy_cleanup = NULL;
178 qcurl_easy_getinfo = NULL;
179 qcurl_easy_duphandle = NULL;
180 qcurl_easy_reset = NULL;
181
182 qcurl_multi_init = NULL;
183 qcurl_multi_add_handle = NULL;
184 qcurl_multi_remove_handle = NULL;
185 qcurl_multi_fdset = NULL;
186 qcurl_multi_perform = NULL;
187 qcurl_multi_cleanup = NULL;
188 qcurl_multi_info_read = NULL;
189 qcurl_multi_strerror = NULL;
190 #endif /* USE_CURL_DLOPEN */
191 }
192
CL_cURL_Cleanup(void)193 void CL_cURL_Cleanup(void)
194 {
195 if(clc.downloadCURLM) {
196 if(clc.downloadCURL) {
197 qcurl_multi_remove_handle(clc.downloadCURLM,
198 clc.downloadCURL);
199 qcurl_easy_cleanup(clc.downloadCURL);
200 }
201 qcurl_multi_cleanup(clc.downloadCURLM);
202 clc.downloadCURLM = NULL;
203 clc.downloadCURL = NULL;
204 }
205 else if(clc.downloadCURL) {
206 qcurl_easy_cleanup(clc.downloadCURL);
207 clc.downloadCURL = NULL;
208 }
209 }
210
CL_cURL_CallbackProgress(void * dummy,double dltotal,double dlnow,double ultotal,double ulnow)211 static int CL_cURL_CallbackProgress( void *dummy, double dltotal, double dlnow,
212 double ultotal, double ulnow )
213 {
214 clc.downloadSize = (int)dltotal;
215 Cvar_SetValue( "cl_downloadSize", clc.downloadSize );
216 clc.downloadCount = (int)dlnow;
217 Cvar_SetValue( "cl_downloadCount", clc.downloadCount );
218 return 0;
219 }
220
CL_cURL_CallbackWrite(void * buffer,size_t size,size_t nmemb,void * stream)221 static size_t CL_cURL_CallbackWrite(void *buffer, size_t size, size_t nmemb,
222 void *stream)
223 {
224 FS_Write( buffer, size*nmemb, ((fileHandle_t*)stream)[0] );
225 return size*nmemb;
226 }
227
CL_cURL_BeginDownload(const char * localName,const char * remoteURL)228 void CL_cURL_BeginDownload( const char *localName, const char *remoteURL )
229 {
230 clc.cURLUsed = qtrue;
231 Com_Printf("URL: %s\n", remoteURL);
232 Com_DPrintf("***** CL_cURL_BeginDownload *****\n"
233 "Localname: %s\n"
234 "RemoteURL: %s\n"
235 "****************************\n", localName, remoteURL);
236 CL_cURL_Cleanup();
237 Q_strncpyz(clc.downloadURL, remoteURL, sizeof(clc.downloadURL));
238 Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName));
239 Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName),
240 "%s.tmp", localName);
241
242 // Set so UI gets access to it
243 Cvar_Set("cl_downloadName", localName);
244 Cvar_Set("cl_downloadSize", "0");
245 Cvar_Set("cl_downloadCount", "0");
246 Cvar_SetValue("cl_downloadTime", cls.realtime);
247
248 clc.downloadBlock = 0; // Starting new file
249 clc.downloadCount = 0;
250
251 clc.downloadCURL = qcurl_easy_init();
252 if(!clc.downloadCURL) {
253 Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_easy_init() "
254 "failed\n");
255 return;
256 }
257 clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName);
258 if(!clc.download) {
259 Com_Error(ERR_DROP, "CL_cURL_BeginDownload: failed to open "
260 "%s for writing\n", clc.downloadTempName);
261 return;
262 }
263 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, clc.download);
264 if(com_developer->integer)
265 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_VERBOSE, 1);
266 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_URL, clc.downloadURL);
267 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_TRANSFERTEXT, 0);
268 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_REFERER, va("ioQ3://%s",
269 NET_AdrToString(clc.serverAddress)));
270 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_USERAGENT, va("%s %s",
271 Q3_VERSION, qcurl_version()));
272 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEFUNCTION,
273 CL_cURL_CallbackWrite);
274 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, &clc.download);
275 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_NOPROGRESS, 0);
276 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSFUNCTION,
277 CL_cURL_CallbackProgress);
278 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSDATA, NULL);
279 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FAILONERROR, 1);
280 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1);
281 qcurl_easy_setopt(clc.downloadCURL, CURLOPT_MAXREDIRS, 5);
282 clc.downloadCURLM = qcurl_multi_init();
283 if(!clc.downloadCURLM) {
284 qcurl_easy_cleanup(clc.downloadCURL);
285 clc.downloadCURL = NULL;
286 Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_multi_init() "
287 "failed\n");
288 return;
289 }
290 qcurl_multi_add_handle(clc.downloadCURLM, clc.downloadCURL);
291
292 if(!(clc.sv_allowDownload & DLF_NO_DISCONNECT) &&
293 !clc.cURLDisconnected) {
294
295 CL_AddReliableCommand("disconnect");
296 CL_WritePacket();
297 CL_WritePacket();
298 CL_WritePacket();
299 clc.cURLDisconnected = qtrue;
300 }
301 }
302
CL_cURL_PerformDownload(void)303 void CL_cURL_PerformDownload(void)
304 {
305 CURLMcode res;
306 CURLMsg *msg;
307 int c;
308 int i = 0;
309
310 res = qcurl_multi_perform(clc.downloadCURLM, &c);
311 while(res == CURLM_CALL_MULTI_PERFORM && i < 100) {
312 res = qcurl_multi_perform(clc.downloadCURLM, &c);
313 i++;
314 }
315 if(res == CURLM_CALL_MULTI_PERFORM)
316 return;
317 msg = qcurl_multi_info_read(clc.downloadCURLM, &c);
318 if(msg == NULL) {
319 return;
320 }
321 FS_FCloseFile(clc.download);
322 if(msg->msg == CURLMSG_DONE && msg->data.result == CURLE_OK) {
323 FS_SV_Rename(clc.downloadTempName, clc.downloadName);
324 clc.downloadRestart = qtrue;
325 }
326 else {
327 long code;
328
329 qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE,
330 &code);
331 Com_Error(ERR_DROP, "Download Error: %s Code: %ld URL: %s",
332 qcurl_easy_strerror(msg->data.result),
333 code, clc.downloadURL);
334 }
335 *clc.downloadTempName = *clc.downloadName = 0;
336 Cvar_Set( "cl_downloadName", "" );
337 CL_NextDownload();
338 }
339 #endif /* USE_CURL */
340