1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2011-2017 - Daniel De Matteis
3 *
4 * RetroArch is free software: you can redistribute it and/or modify it under the terms
5 * of the GNU General Public License as published by the Free Software Found-
6 * ation, either version 3 of the License, or (at your option) any later version.
7 *
8 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10 * PURPOSE. See the GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License along with RetroArch.
13 * If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 #include <stdlib.h>
17
18 #include <net/net_http.h>
19 #include <string/stdstring.h>
20 #include <compat/strl.h>
21 #include <file/file_path.h>
22 #include <net/net_compat.h>
23 #include <retro_timers.h>
24
25 #ifdef RARCH_INTERNAL
26 #include "../gfx/video_display_server.h"
27 #endif
28 #include "task_file_transfer.h"
29 #include "tasks_internal.h"
30
31 enum http_status_enum
32 {
33 HTTP_STATUS_CONNECTION_TRANSFER = 0,
34 HTTP_STATUS_CONNECTION_TRANSFER_PARSE,
35 HTTP_STATUS_TRANSFER,
36 HTTP_STATUS_TRANSFER_PARSE,
37 HTTP_STATUS_TRANSFER_PARSE_FREE
38 };
39
40 struct http_transfer_info
41 {
42 int progress;
43 char url[255];
44 };
45
46 struct http_handle
47 {
48 struct http_t *handle;
49 transfer_cb_t cb;
50 struct
51 {
52 struct http_connection_t *handle;
53 transfer_cb_t cb;
54 } connection;
55 unsigned status;
56 bool error;
57 char connection_elem[255];
58 char connection_url[255];
59 };
60
61 typedef struct http_transfer_info http_transfer_info_t;
62 typedef struct http_handle http_handle_t;
63
task_http_con_iterate_transfer(http_handle_t * http)64 static int task_http_con_iterate_transfer(http_handle_t *http)
65 {
66 if (!net_http_connection_iterate(http->connection.handle))
67 return -1;
68 return 0;
69 }
70
task_http_conn_iterate_transfer_parse(http_handle_t * http)71 static int task_http_conn_iterate_transfer_parse(
72 http_handle_t *http)
73 {
74 if (net_http_connection_done(http->connection.handle))
75 {
76 if (http->connection.handle && http->connection.cb)
77 http->connection.cb(http, 0);
78 }
79
80 net_http_connection_free(http->connection.handle);
81
82 http->connection.handle = NULL;
83
84 return 0;
85 }
86
cb_http_conn_default(void * data_,size_t len)87 static int cb_http_conn_default(void *data_, size_t len)
88 {
89 http_handle_t *http = (http_handle_t*)data_;
90
91 if (!http)
92 return -1;
93
94 if (!network_init())
95 return -1;
96
97 http->handle = net_http_new(http->connection.handle);
98
99 if (!http->handle)
100 {
101 http->error = true;
102 return -1;
103 }
104
105 http->cb = NULL;
106
107 return 0;
108 }
109
110 /**
111 * task_http_iterate_transfer:
112 *
113 * Resumes HTTP transfer update.
114 *
115 * Returns: 0 when finished, -1 when we should continue
116 * with the transfer on the next frame.
117 **/
task_http_iterate_transfer(retro_task_t * task)118 static int task_http_iterate_transfer(retro_task_t *task)
119 {
120 http_handle_t *http = (http_handle_t*)task->state;
121 size_t pos = 0, tot = 0;
122
123 /* FIXME: This wouldn't be needed if we could wait for a timeout */
124 if (task_queue_is_threaded())
125 retro_sleep(1);
126
127 if (!net_http_update(http->handle, &pos, &tot))
128 {
129 if (tot == 0)
130 task_set_progress(task, -1);
131 else if (pos < (((size_t)-1) / 100))
132 /* prefer multiply then divide for more accurate results */
133 task_set_progress(task, (signed)(pos * 100 / tot));
134 else
135 /* but invert the logic if it would cause an overflow */
136 task_set_progress(task, MIN((signed)pos / (tot / 100), 100));
137 return -1;
138 }
139
140 return 0;
141 }
142
task_http_transfer_handler(retro_task_t * task)143 static void task_http_transfer_handler(retro_task_t *task)
144 {
145 http_transfer_data_t *data = NULL;
146 http_handle_t *http = (http_handle_t*)task->state;
147
148 if (task_get_cancelled(task))
149 goto task_finished;
150
151 switch (http->status)
152 {
153 case HTTP_STATUS_CONNECTION_TRANSFER_PARSE:
154 task_http_conn_iterate_transfer_parse(http);
155 http->status = HTTP_STATUS_TRANSFER;
156 break;
157 case HTTP_STATUS_CONNECTION_TRANSFER:
158 if (!task_http_con_iterate_transfer(http))
159 http->status = HTTP_STATUS_CONNECTION_TRANSFER_PARSE;
160 break;
161 case HTTP_STATUS_TRANSFER:
162 if (!task_http_iterate_transfer(task))
163 goto task_finished;
164 break;
165 case HTTP_STATUS_TRANSFER_PARSE:
166 goto task_finished;
167 default:
168 break;
169 }
170
171 if (http->error)
172 goto task_finished;
173
174 return;
175 task_finished:
176 task_set_finished(task, true);
177
178 if (http->handle)
179 {
180 size_t len = 0;
181 char *tmp = (char*)net_http_data(http->handle, &len, false);
182
183 if (tmp && http->cb)
184 http->cb(tmp, len);
185
186 if (net_http_error(http->handle) || task_get_cancelled(task))
187 {
188 tmp = (char*)net_http_data(http->handle, &len, true);
189
190 if (tmp)
191 free(tmp);
192
193 if (task_get_cancelled(task))
194 {
195 task_set_error(task, strdup("Task cancelled."));
196 }
197 else
198 {
199 data = (http_transfer_data_t*)malloc(sizeof(*data));
200 data->data = NULL;
201 data->len = 0;
202 data->status = net_http_status(http->handle);
203
204 task_set_data(task, data);
205
206 if (!task->mute)
207 task_set_error(task, strdup("Download failed."));
208 }
209 }
210 else
211 {
212 data = (http_transfer_data_t*)malloc(sizeof(*data));
213 data->data = tmp;
214 data->len = len;
215 data->status = net_http_status(http->handle);
216
217 task_set_data(task, data);
218 }
219
220 net_http_delete(http->handle);
221 } else if (http->error)
222 task_set_error(task, strdup("Internal error."));
223
224 free(http);
225 }
226
task_http_transfer_cleanup(retro_task_t * task)227 static void task_http_transfer_cleanup(retro_task_t *task)
228 {
229 http_transfer_data_t* data = (http_transfer_data_t*)task_get_data(task);
230 if (data)
231 {
232 if (data->data)
233 free(data->data);
234 free(data);
235 }
236 }
237
task_http_finder(retro_task_t * task,void * user_data)238 static bool task_http_finder(retro_task_t *task, void *user_data)
239 {
240 http_handle_t *http = NULL;
241
242 if (!task || (task->handler != task_http_transfer_handler))
243 return false;
244
245 if (!user_data)
246 return false;
247
248 http = (http_handle_t*)task->state;
249 if (!http)
250 return false;
251
252 return string_is_equal(http->connection_url, (const char*)user_data);
253 }
254
task_http_retriever(retro_task_t * task,void * data)255 static bool task_http_retriever(retro_task_t *task, void *data)
256 {
257 http_transfer_info_t *info = (http_transfer_info_t*)data;
258
259 /* Extract HTTP handle and return already if invalid */
260 http_handle_t *http = (http_handle_t *)task->state;
261 if (!http)
262 return false;
263
264 /* Fill HTTP info link */
265 strlcpy(info->url, http->connection_url, sizeof(info->url));
266 info->progress = task_get_progress(task);
267 return true;
268 }
269
http_transfer_progress_cb(retro_task_t * task)270 static void http_transfer_progress_cb(retro_task_t *task)
271 {
272 #ifdef RARCH_INTERNAL
273 if (task)
274 video_display_server_set_window_progress(task->progress, task->finished);
275 #endif
276 }
277
task_push_http_transfer_generic(struct http_connection_t * conn,const char * url,bool mute,const char * type,retro_task_callback_t cb,void * user_data)278 static void* task_push_http_transfer_generic(
279 struct http_connection_t *conn,
280 const char *url, bool mute, const char *type,
281 retro_task_callback_t cb, void *user_data)
282 {
283 task_finder_data_t find_data;
284 retro_task_t *t = NULL;
285 http_handle_t *http = NULL;
286
287 find_data.func = task_http_finder;
288 find_data.userdata = (void*)url;
289
290 /* Concurrent download of the same file is not allowed */
291 if (task_queue_find(&find_data))
292 {
293 if (conn)
294 net_http_connection_free(conn);
295
296 return NULL;
297 }
298
299 if (!conn)
300 return NULL;
301
302 http = (http_handle_t*)malloc(sizeof(*http));
303
304 if (!http)
305 goto error;
306
307 http->connection.handle = conn;
308 http->connection.cb = &cb_http_conn_default;
309 http->connection_elem[0] = '\0';
310 http->connection_url[0] = '\0';
311 http->handle = NULL;
312 http->cb = NULL;
313 http->status = 0;
314 http->error = false;
315
316 if (type)
317 strlcpy(http->connection_elem, type, sizeof(http->connection_elem));
318
319 strlcpy(http->connection_url, url, sizeof(http->connection_url));
320
321 http->status = HTTP_STATUS_CONNECTION_TRANSFER;
322 t = task_init();
323
324 if (!t)
325 goto error;
326
327 t->handler = task_http_transfer_handler;
328 t->state = http;
329 t->mute = mute;
330 t->callback = cb;
331 t->progress_cb = http_transfer_progress_cb;
332 t->cleanup = task_http_transfer_cleanup;
333 t->user_data = user_data;
334 t->progress = -1;
335
336 task_queue_push(t);
337
338 return t;
339
340 error:
341 if (conn)
342 net_http_connection_free(conn);
343 if (http)
344 free(http);
345
346 return NULL;
347 }
348
task_push_http_transfer(const char * url,bool mute,const char * type,retro_task_callback_t cb,void * user_data)349 void* task_push_http_transfer(const char *url, bool mute,
350 const char *type,
351 retro_task_callback_t cb, void *user_data)
352 {
353 if (string_is_empty(url))
354 return NULL;
355
356 return task_push_http_transfer_generic(
357 net_http_connection_new(url, "GET", NULL),
358 url, mute, type, cb, user_data);
359 }
360
task_push_http_transfer_file(const char * url,bool mute,const char * type,retro_task_callback_t cb,file_transfer_t * transfer_data)361 void* task_push_http_transfer_file(const char* url, bool mute,
362 const char* type,
363 retro_task_callback_t cb, file_transfer_t* transfer_data)
364 {
365 const char *s = NULL;
366 char tmp[255] = "";
367 retro_task_t *t = NULL;
368
369 if (string_is_empty(url))
370 return NULL;
371
372 t = (retro_task_t*)task_push_http_transfer_generic(
373 net_http_connection_new(url, "GET", NULL),
374 url, mute, type, cb, transfer_data);
375
376 if (!t)
377 return NULL;
378
379 if (transfer_data)
380 s = transfer_data->path;
381 else
382 s = url;
383
384 strlcpy(tmp, msg_hash_to_str(MSG_DOWNLOADING), sizeof(tmp));
385 strlcat(tmp, " ", sizeof(tmp));
386
387 if (string_ends_with_size(s, ".index",
388 strlen(s), STRLEN_CONST(".index")))
389 strlcat(tmp, msg_hash_to_str(MSG_INDEX_FILE), sizeof(tmp));
390 else
391 strlcat(tmp, s, sizeof(tmp));
392
393 t->title = strdup(tmp);
394 return t;
395 }
396
task_push_http_transfer_with_user_agent(const char * url,bool mute,const char * type,const char * user_agent,retro_task_callback_t cb,void * user_data)397 void* task_push_http_transfer_with_user_agent(const char *url, bool mute,
398 const char *type, const char* user_agent,
399 retro_task_callback_t cb, void *user_data)
400 {
401 struct http_connection_t* conn;
402
403 if (string_is_empty(url))
404 return NULL;
405
406 conn = net_http_connection_new(url, "GET", NULL);
407 if (!conn)
408 return NULL;
409
410 if (user_agent != NULL)
411 net_http_connection_set_user_agent(conn, user_agent);
412
413 /* assert: task_push_http_transfer_generic will free conn on failure */
414 return task_push_http_transfer_generic(conn, url, mute, type, cb, user_data);
415 }
416
task_push_http_post_transfer(const char * url,const char * post_data,bool mute,const char * type,retro_task_callback_t cb,void * user_data)417 void* task_push_http_post_transfer(const char *url,
418 const char *post_data, bool mute,
419 const char *type, retro_task_callback_t cb, void *user_data)
420 {
421 if (string_is_empty(url))
422 return NULL;
423 return task_push_http_transfer_generic(
424 net_http_connection_new(url, "POST", post_data),
425 url, mute, type, cb, user_data);
426 }
427
task_push_http_post_transfer_with_user_agent(const char * url,const char * post_data,bool mute,const char * type,const char * user_agent,retro_task_callback_t cb,void * user_data)428 void* task_push_http_post_transfer_with_user_agent(const char *url,
429 const char *post_data, bool mute,
430 const char *type, const char* user_agent,
431 retro_task_callback_t cb, void *user_data)
432 {
433 struct http_connection_t* conn;
434
435 if (string_is_empty(url))
436 return NULL;
437
438 conn = net_http_connection_new(url, "POST", post_data);
439 if (!conn)
440 return NULL;
441
442 if (user_agent != NULL)
443 net_http_connection_set_user_agent(conn, user_agent);
444
445 /* assert: task_push_http_transfer_generic will free conn on failure */
446 return task_push_http_transfer_generic(conn, url, mute, type, cb, user_data);
447 }
448
http_task_get_transfer_list(void)449 task_retriever_info_t *http_task_get_transfer_list(void)
450 {
451 task_retriever_data_t retrieve_data;
452
453 /* Fill retrieve data */
454 retrieve_data.handler = task_http_transfer_handler;
455 retrieve_data.element_size = sizeof(http_transfer_info_t);
456 retrieve_data.func = task_http_retriever;
457
458 /* Build list of current HTTP transfers and return it */
459 task_queue_retrieve(&retrieve_data);
460
461 return retrieve_data.list;
462 }
463