1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 
21 #include "http.h"
22 #ifndef NO_HTTP
23 #include "../shared/shared.h"
24 #include <SDL_thread.h>
25 
26 /**
27  * @brief Extract the servername, the port and the path part of the given url
28  * @param url The url to extract the data from
29  * @param server The server target buffer
30  * @param serverLength The length of the buffer
31  * @param path The path target buffer
32  * @param pathLength The length of the buffer
33  * @param port The port
34  * @return @c true if the extracting went well, @c false if an error occurred
35  */
HTTP_ExtractComponents(const char * url,char * server,size_t serverLength,char * path,size_t pathLength,int * port)36 bool HTTP_ExtractComponents (const char* url, char* server, size_t serverLength, char* path, size_t pathLength, int* port)
37 {
38 	char* s, *buf;
39 	const char* proto = "http://";
40 	const size_t protoLength = strlen(proto);
41 	char buffer[1024];
42 	int i;
43 
44 	if (Q_strncasecmp(proto, url, protoLength))
45 		return false;
46 
47 	Q_strncpyz(buffer, url, sizeof(buffer));
48 	buf = buffer;
49 
50 	buf += protoLength;
51 	i = 0;
52 	for (s = server; *buf != '\0' && *buf != ':' && *buf != '/';) {
53 		if (i >= serverLength - 1)
54 			return false;
55 		i++;
56 		*s++ = *buf++;
57 	}
58 	*s = '\0';
59 
60 	if (*buf == ':') {
61 		buf++;
62 		if (sscanf(buf, "%d", port) != 1)
63 			return false;
64 
65 		for (buf++; *buf != '\0' && *buf != '/'; buf++) {
66 		}
67 	} else {
68 		*port = 80;
69 	}
70 
71 	Q_strncpyz(path, buf, pathLength);
72 
73 	return true;
74 }
75 
76 /**
77  * @brief libcurl callback to update header info.
78  */
HTTP_Header(void * ptr,size_t size,size_t nmemb,void * stream)79 size_t HTTP_Header (void* ptr, size_t size, size_t nmemb, void* stream)
80 {
81 	char headerBuff[1024];
82 	size_t bytes;
83 	size_t len;
84 
85 	bytes = size * nmemb;
86 
87 	if (bytes <= 16)
88 		return bytes;
89 
90 	if (bytes < sizeof(headerBuff))
91 		len = bytes + 1;
92 	else
93 		len = sizeof(headerBuff);
94 
95 	Q_strncpyz(headerBuff, (const char*)ptr, len);
96 
97 	if (!Q_strncasecmp(headerBuff, "Content-Length: ", 16)) {
98 		dlhandle_t* dl = (dlhandle_t*)stream;
99 		if (dl->file)
100 			dl->fileSize = strtoul(headerBuff + 16, nullptr, 10);
101 	}
102 
103 	return bytes;
104 }
105 
106 /**
107  * @brief libcurl callback for HTTP_GetURL
108  */
HTTP_Recv(void * ptr,size_t size,size_t nmemb,void * stream)109 size_t HTTP_Recv (void* ptr, size_t size, size_t nmemb, void* stream)
110 {
111 	size_t bytes;
112 	dlhandle_t* dl;
113 
114 	dl = (dlhandle_t*)stream;
115 
116 	bytes = size * nmemb;
117 
118 	if (!dl->fileSize) {
119 		dl->fileSize = bytes > 131072 ? bytes : 131072;
120 		dl->tempBuffer = Mem_AllocTypeN(char, dl->fileSize);
121 	} else if (dl->position + bytes >= dl->fileSize - 1) {
122 		char* tmp = dl->tempBuffer;
123 		dl->tempBuffer = Mem_AllocTypeN(char, dl->fileSize * 2);
124 		memcpy(dl->tempBuffer, tmp, dl->fileSize);
125 		Mem_Free(tmp);
126 		dl->fileSize *= 2;
127 	}
128 
129 	memcpy(dl->tempBuffer + dl->position, ptr, bytes);
130 	dl->position += bytes;
131 	dl->tempBuffer[dl->position] = 0;
132 
133 	return bytes;
134 }
135 
136 /**
137  * @brief Converts the hostname into an ip to work around a bug in libcurl (resp. the resolver) that
138  * uses alarm for timeouts (this is in conflict with our signal handlers and longjmp environment)
139  * @param[in] url The url to convert
140  * @param[out] buf The resolved url or empty if an error occurred
141  * @param[in] size The size of the target buffer
142  */
HTTP_ResolvURL(const char * url,char * buf,size_t size)143 static void HTTP_ResolvURL (const char* url, char* buf, size_t size)
144 {
145 	char server[512];
146 	char ipServer[MAX_VAR];
147 	int port;
148 	char uriPath[512];
149 
150 	buf[0] = '\0';
151 
152 	if (!HTTP_ExtractComponents(url, server, sizeof(server), uriPath, sizeof(uriPath), &port))
153 		Com_Error(ERR_DROP, "invalid url given: %s", url);
154 
155 	NET_ResolvNode(server, ipServer, sizeof(ipServer));
156 	if (ipServer[0] != '\0')
157 		Com_sprintf(buf, size, "http://%s:%i%s", ipServer, port, uriPath);
158 }
159 
160 /**
161  * @brief Gets a specific url
162  * @note Make sure, that you free the string that is returned by this function
163  */
HTTP_GetURLInternal(dlhandle_t & dl,const char * url,FILE * file,const char * postfields)164 static bool HTTP_GetURLInternal (dlhandle_t &dl, const char* url, FILE* file, const char* postfields)
165 {
166 	if (Q_strnull(url)) {
167 		Com_Printf("invalid url given\n");
168 		return false;
169 	}
170 
171 	char buf[576];
172 	HTTP_ResolvURL(url, buf, sizeof(buf));
173 	if (buf[0] == '\0') {
174 		Com_Printf("could not resolve '%s'\n", url);
175 		return false;
176 	}
177 	Q_strncpyz(dl.URL, url, sizeof(dl.URL));
178 
179 	dl.curl = curl_easy_init();
180 	curl_easy_setopt(dl.curl, CURLOPT_CONNECTTIMEOUT, http_timeout->integer);
181 	curl_easy_setopt(dl.curl, CURLOPT_TIMEOUT, http_timeout->integer);
182 	curl_easy_setopt(dl.curl, CURLOPT_ENCODING, "");
183 	curl_easy_setopt(dl.curl, CURLOPT_NOPROGRESS, 1);
184 	curl_easy_setopt(dl.curl, CURLOPT_FAILONERROR, 1);
185 	if (file) {
186 		curl_easy_setopt(dl.curl, CURLOPT_WRITEDATA, file);
187 		curl_easy_setopt(dl.curl, CURLOPT_WRITEFUNCTION, nullptr);
188 	} else {
189 		curl_easy_setopt(dl.curl, CURLOPT_WRITEDATA, &dl);
190 		curl_easy_setopt(dl.curl, CURLOPT_WRITEFUNCTION, HTTP_Recv);
191 	}
192 	curl_easy_setopt(dl.curl, CURLOPT_PROXY, http_proxy->string);
193 	curl_easy_setopt(dl.curl, CURLOPT_FOLLOWLOCATION, 1);
194 	curl_easy_setopt(dl.curl, CURLOPT_MAXREDIRS, 5);
195 	curl_easy_setopt(dl.curl, CURLOPT_WRITEHEADER, &dl);
196 	if (postfields != nullptr)
197 		curl_easy_setopt(dl.curl, CURLOPT_POSTFIELDS, postfields);
198 	curl_easy_setopt(dl.curl, CURLOPT_HEADERFUNCTION, HTTP_Header);
199 	curl_easy_setopt(dl.curl, CURLOPT_USERAGENT, GAME_TITLE " " UFO_VERSION);
200 	curl_easy_setopt(dl.curl, CURLOPT_URL, dl.URL);
201 	curl_easy_setopt(dl.curl, CURLOPT_NOSIGNAL, 1);
202 
203 	/* get it */
204 	const CURLcode result = curl_easy_perform(dl.curl);
205 	if (result != CURLE_OK)	{
206 		if (result == CURLE_HTTP_RETURNED_ERROR) {
207 			long httpCode = 0;
208 			curl_easy_getinfo(dl.curl, CURLINFO_RESPONSE_CODE, &httpCode);
209 			Com_Printf("failed to fetch '%s': %s (%i)\n", url, curl_easy_strerror(result), (int)httpCode);
210 		} else {
211 			Com_Printf("failed to fetch '%s': %s\n", url, curl_easy_strerror(result));
212 		}
213 		curl_easy_cleanup(dl.curl);
214 		return false;
215 	}
216 
217 	/* clean up */
218 	curl_easy_cleanup(dl.curl);
219 
220 	return true;
221 }
222 
HTTP_PutFile(const char * formName,const char * fileName,const char * url,const upparam_t * params)223 bool HTTP_PutFile (const char* formName, const char* fileName, const char* url, const upparam_t* params)
224 {
225 	if (Q_strnull(url)) {
226 		Com_Printf("no upload url given\n");
227 		return false;
228 	}
229 
230 	if (Q_strnull(fileName)) {
231 		Com_Printf("no upload fileName given\n");
232 		return false;
233 	}
234 
235 	if (Q_strnull(formName)) {
236 		Com_Printf("no upload formName given\n");
237 		return false;
238 	}
239 
240 	char buf[576];
241 	HTTP_ResolvURL(url, buf, sizeof(buf));
242 	if (buf[0] == '\0') {
243 		Com_Printf("could not resolve '%s'\n", url);
244 		return false;
245 	}
246 
247 	CURL *curl = curl_easy_init();
248 	if (curl == nullptr) {
249 		Com_Printf("could not init curl\n");
250 		return false;
251 	}
252 
253 	struct curl_httppost* post = nullptr;
254 	struct curl_httppost* last = nullptr;
255 	while (params) {
256 		curl_formadd(&post, &last, CURLFORM_PTRNAME, params->name, CURLFORM_PTRCONTENTS, params->value, CURLFORM_END);
257 		params = params->next;
258 	}
259 
260 	curl_formadd(&post, &last, CURLFORM_PTRNAME, formName, CURLFORM_FILE, fileName, CURLFORM_END);
261 
262 	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, http_timeout->integer);
263 	curl_easy_setopt(curl, CURLOPT_TIMEOUT, http_timeout->integer);
264 	curl_easy_setopt(curl, CURLOPT_HTTPPOST, post);
265 	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
266 	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
267 	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
268 	curl_easy_setopt(curl, CURLOPT_USERAGENT, GAME_TITLE " " UFO_VERSION);
269 	curl_easy_setopt(curl, CURLOPT_URL, url);
270 	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
271 	const CURLcode result = curl_easy_perform(curl);
272 	if (result != CURLE_OK)	{
273 		if (result == CURLE_HTTP_RETURNED_ERROR) {
274 			long httpCode = 0;
275 			curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
276 			Com_Printf("failed to upload file '%s': %s (%i)\n", fileName, curl_easy_strerror(result), (int)httpCode);
277 		} else {
278 			Com_Printf("failed to upload file '%s': %s\n", fileName, curl_easy_strerror(result));
279 		}
280 		curl_easy_cleanup(curl);
281 		return false;
282 	}
283 
284 	curl_easy_cleanup(curl);
285 	return true;
286 }
287 
288 /**
289  * @brief Downloads the given @c url into the given @c file.
290  * @param[in] url The url to fetch
291  * @param[in] file The file to write the result into
292  * @param[in] postfields Some potential POST data in the form
293  */
HTTP_GetToFile(const char * url,FILE * file,const char * postfields)294 bool HTTP_GetToFile (const char* url, FILE* file, const char* postfields)
295 {
296 	if (!file)
297 		return false;
298 	dlhandle_t dl;
299 	OBJZERO(dl);
300 
301 	return HTTP_GetURLInternal(dl, url, file, postfields);
302 }
303 
304 /**
305  * @brief This function converts the given url to an URL encoded string.
306  * All input characters that are not a-z, A-Z, 0-9, '-', '.', '_' or '~' are converted to their "URL escaped" version
307  * (%NN where NN is a two-digit hexadecimal number).
308  * @return @c true if the conversion was successful, @c false if it failed or the target buffer was too small.
309  */
HTTP_Encode(const char * url,char * out,size_t outLength)310 bool HTTP_Encode (const char* url, char* out, size_t outLength)
311 {
312 	CURL *curl = curl_easy_init();
313 	char* encoded = curl_easy_escape(curl, url, 0);
314 	if (encoded == nullptr) {
315 		curl_easy_cleanup(curl);
316 		return false;
317 	}
318 	Q_strncpyz(out, encoded, outLength);
319 	const bool success = strlen(encoded) < outLength;
320 	curl_free(encoded);
321 	curl_easy_cleanup(curl);
322 	return success;
323 }
324 
325 /**
326  * @brief Downloads the given @c url and return the data to the callback (optional)
327  * @param[in] url The url to fetch
328  * @param[in] callback The callback to give the data to. Might also be @c NULL
329  * @param[in] userdata The userdata that is given to the callback
330  * @param[in] postfields Some potential POST data
331  */
HTTP_GetURL(const char * url,http_callback_t callback,void * userdata,const char * postfields)332 bool HTTP_GetURL (const char* url, http_callback_t callback, void* userdata, const char* postfields)
333 {
334 	dlhandle_t dl;
335 	OBJZERO(dl);
336 
337 	if (!HTTP_GetURLInternal(dl, url, nullptr, postfields)) {
338 		Mem_Free(dl.tempBuffer);
339 		dl.tempBuffer = nullptr;
340 		return false;
341 	}
342 
343 	if (callback != nullptr)
344 		callback(dl.tempBuffer, userdata);
345 
346 	Mem_Free(dl.tempBuffer);
347 	dl.tempBuffer = nullptr;
348 	return true;
349 }
350 
351 /**
352  * @brief UFO is exiting or we're changing servers. Clean up.
353  */
HTTP_Cleanup(void)354 void HTTP_Cleanup (void)
355 {
356 	curl_global_cleanup();
357 }
358 #else
HTTP_GetURL(const char * url,http_callback_t callback)359 void HTTP_GetURL(const char* url, http_callback_t callback) {}
HTTP_PutFile(const char * formName,const char * fileName,const char * url,const upparam_t * params)360 void HTTP_PutFile(const char* formName, const char* fileName, const char* url, const upparam_t* params) {}
HTTP_Recv(void * ptr,size_t size,size_t nmemb,void * stream)361 size_t HTTP_Recv(void* ptr, size_t size, size_t nmemb, void* stream) {return 0L;}
HTTP_Header(void * ptr,size_t size,size_t nmemb,void * stream)362 size_t HTTP_Header(void* ptr, size_t size, size_t nmemb, void* stream) {return 0L;}
HTTP_Cleanup(void)363 void HTTP_Cleanup(void) {}
HTTP_ExtractComponents(const char * url,char * server,size_t serverLength,char * path,size_t pathLength,int * port)364 bool HTTP_ExtractComponents(const char* url, char* server, size_t serverLength, char* path, size_t pathLength, int* port) {return false;}
365 #endif
366