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