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