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