1 /* Copyright  (C) 2010-2019 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (core_updater_list.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <file/file_path.h>
24 #include <string/stdstring.h>
25 #include <lists/string_list.h>
26 #include <net/net_http.h>
27 #include <array/rbuf.h>
28 #include <retro_miscellaneous.h>
29 
30 #include "file_path_special.h"
31 #include "core_info.h"
32 
33 #include "core_updater_list.h"
34 
35 /* Holds all entries in a core updater list */
36 struct core_updater_list
37 {
38    core_updater_list_entry_t *entries;
39    enum core_updater_list_type type;
40 };
41 
42 /* Cached ('global') core updater list */
43 static core_updater_list_t *core_list_cached = NULL;
44 
45 /**************************************/
46 /* Initialisation / De-Initialisation */
47 /**************************************/
48 
49 /* Frees contents of specified core updater
50  * list entry */
core_updater_list_free_entry(core_updater_list_entry_t * entry)51 static void core_updater_list_free_entry(core_updater_list_entry_t *entry)
52 {
53    if (!entry)
54       return;
55 
56    if (entry->remote_filename)
57    {
58       free(entry->remote_filename);
59       entry->remote_filename = NULL;
60    }
61 
62    if (entry->remote_core_path)
63    {
64       free(entry->remote_core_path);
65       entry->remote_core_path = NULL;
66    }
67 
68    if (entry->local_core_path)
69    {
70       free(entry->local_core_path);
71       entry->local_core_path = NULL;
72    }
73 
74    if (entry->local_info_path)
75    {
76       free(entry->local_info_path);
77       entry->local_info_path = NULL;
78    }
79 
80    if (entry->display_name)
81    {
82       free(entry->display_name);
83       entry->display_name = NULL;
84    }
85 
86    if (entry->description)
87    {
88       free(entry->description);
89       entry->description = NULL;
90    }
91 
92    if (entry->licenses_list)
93    {
94       string_list_free(entry->licenses_list);
95       entry->licenses_list = NULL;
96    }
97 }
98 
99 /* Creates a new, empty core updater list.
100  * Returns a handle to a new core_updater_list_t object
101  * on success, otherwise returns NULL. */
core_updater_list_init(void)102 core_updater_list_t *core_updater_list_init(void)
103 {
104    /* Create core updater list */
105    core_updater_list_t *core_list = (core_updater_list_t*)
106          malloc(sizeof(*core_list));
107 
108    if (!core_list)
109       return NULL;
110 
111    /* Initialise members */
112    core_list->entries = NULL;
113    core_list->type    = CORE_UPDATER_LIST_TYPE_UNKNOWN;
114 
115    return core_list;
116 }
117 
118 /* Resets (removes all entries of) specified core
119  * updater list */
core_updater_list_reset(core_updater_list_t * core_list)120 void core_updater_list_reset(core_updater_list_t *core_list)
121 {
122    if (!core_list)
123       return;
124 
125    if (core_list->entries)
126    {
127       size_t i;
128 
129       for (i = 0; i < RBUF_LEN(core_list->entries); i++)
130          core_updater_list_free_entry(&core_list->entries[i]);
131 
132       RBUF_FREE(core_list->entries);
133    }
134 
135    core_list->type = CORE_UPDATER_LIST_TYPE_UNKNOWN;
136 }
137 
138 /* Frees specified core updater list */
core_updater_list_free(core_updater_list_t * core_list)139 void core_updater_list_free(core_updater_list_t *core_list)
140 {
141    if (!core_list)
142       return;
143 
144    core_updater_list_reset(core_list);
145    free(core_list);
146 }
147 
148 /***************/
149 /* Cached List */
150 /***************/
151 
152 /* Creates a new, empty cached core updater list
153  * (i.e. 'global' list).
154  * Returns false in the event of an error. */
core_updater_list_init_cached(void)155 bool core_updater_list_init_cached(void)
156 {
157    /* Free any existing cached core updater list */
158    if (core_list_cached)
159    {
160       core_updater_list_free(core_list_cached);
161       core_list_cached = NULL;
162    }
163 
164    core_list_cached = core_updater_list_init();
165 
166    if (!core_list_cached)
167       return false;
168 
169    return true;
170 }
171 
172 /* Fetches cached core updater list */
core_updater_list_get_cached(void)173 core_updater_list_t *core_updater_list_get_cached(void)
174 {
175    if (core_list_cached)
176       return core_list_cached;
177 
178    return NULL;
179 }
180 
181 /* Frees cached core updater list */
core_updater_list_free_cached(void)182 void core_updater_list_free_cached(void)
183 {
184    core_updater_list_free(core_list_cached);
185    core_list_cached = NULL;
186 }
187 
188 /***********/
189 /* Getters */
190 /***********/
191 
192 /* Returns number of entries in core updater list */
core_updater_list_size(core_updater_list_t * core_list)193 size_t core_updater_list_size(core_updater_list_t *core_list)
194 {
195    if (!core_list)
196       return 0;
197 
198    return RBUF_LEN(core_list->entries);
199 }
200 
201 /* Returns 'type' (core delivery method) of
202  * specified core updater list */
core_updater_list_get_type(core_updater_list_t * core_list)203 enum core_updater_list_type core_updater_list_get_type(
204       core_updater_list_t *core_list)
205 {
206    if (!core_list)
207       return CORE_UPDATER_LIST_TYPE_UNKNOWN;
208 
209    return core_list->type;
210 }
211 
212 /* Fetches core updater list entry corresponding
213  * to the specified entry index.
214  * Returns false if index is invalid. */
core_updater_list_get_index(core_updater_list_t * core_list,size_t idx,const core_updater_list_entry_t ** entry)215 bool core_updater_list_get_index(
216       core_updater_list_t *core_list,
217       size_t idx,
218       const core_updater_list_entry_t **entry)
219 {
220    if (!core_list || !entry)
221       return false;
222 
223    if (idx >= RBUF_LEN(core_list->entries))
224       return false;
225 
226    *entry = &core_list->entries[idx];
227 
228    return true;
229 }
230 
231 /* Fetches core updater list entry corresponding
232  * to the specified remote core filename.
233  * Returns false if core is not found. */
core_updater_list_get_filename(core_updater_list_t * core_list,const char * remote_filename,const core_updater_list_entry_t ** entry)234 bool core_updater_list_get_filename(
235       core_updater_list_t *core_list,
236       const char *remote_filename,
237       const core_updater_list_entry_t **entry)
238 {
239    size_t num_entries;
240    size_t i;
241 
242    if (!core_list || !entry || string_is_empty(remote_filename))
243       return false;
244 
245    num_entries = RBUF_LEN(core_list->entries);
246 
247    if (num_entries < 1)
248       return false;
249 
250    /* Search for specified filename */
251    for (i = 0; i < num_entries; i++)
252    {
253       core_updater_list_entry_t *current_entry = &core_list->entries[i];
254 
255       if (string_is_empty(current_entry->remote_filename))
256          continue;
257 
258       if (string_is_equal(remote_filename, current_entry->remote_filename))
259       {
260          *entry = current_entry;
261          return true;
262       }
263    }
264 
265    return false;
266 }
267 
268 /* Fetches core updater list entry corresponding
269  * to the specified core.
270  * Returns false if core is not found. */
core_updater_list_get_core(core_updater_list_t * core_list,const char * local_core_path,const core_updater_list_entry_t ** entry)271 bool core_updater_list_get_core(
272       core_updater_list_t *core_list,
273       const char *local_core_path,
274       const core_updater_list_entry_t **entry)
275 {
276    bool resolve_symlinks;
277    size_t num_entries;
278    size_t i;
279    char real_core_path[PATH_MAX_LENGTH];
280 
281    real_core_path[0] = '\0';
282 
283    if (!core_list || !entry || string_is_empty(local_core_path))
284       return false;
285 
286    num_entries = RBUF_LEN(core_list->entries);
287 
288    if (num_entries < 1)
289       return false;
290 
291    /* Resolve absolute pathname of local_core_path */
292    strlcpy(real_core_path, local_core_path, sizeof(real_core_path));
293    /* Can't resolve symlinks when dealing with cores
294     * installed via play feature delivery, because the
295     * source files have non-standard file names (which
296     * will not be recognised by regular core handling
297     * routines) */
298    resolve_symlinks = (core_list->type != CORE_UPDATER_LIST_TYPE_PFD);
299    path_resolve_realpath(real_core_path, sizeof(real_core_path),
300          resolve_symlinks);
301 
302    if (string_is_empty(real_core_path))
303       return false;
304 
305    /* Search for specified core */
306    for (i = 0; i < num_entries; i++)
307    {
308       core_updater_list_entry_t *current_entry = &core_list->entries[i];
309 
310       if (string_is_empty(current_entry->local_core_path))
311          continue;
312 
313 #ifdef _WIN32
314       /* Handle case-insensitive operating systems*/
315       if (string_is_equal_noncase(real_core_path, current_entry->local_core_path))
316       {
317 #else
318       if (string_is_equal(real_core_path, current_entry->local_core_path))
319       {
320 #endif
321          *entry = current_entry;
322          return true;
323       }
324    }
325 
326    return false;
327 }
328 
329 /***********/
330 /* Setters */
331 /***********/
332 
333 /* Parses date string and adds contents to
334  * specified core updater list entry */
335 static bool core_updater_list_set_date(
336       core_updater_list_entry_t *entry, const char *date_str)
337 {
338    struct string_list date_list = {0};
339 
340    if (!entry || string_is_empty(date_str))
341       goto error;
342 
343    /* Split date string into component values */
344    string_list_initialize(&date_list);
345    if (!string_split_noalloc(&date_list, date_str, "-"))
346          goto error;
347 
348    /* Date string must have 3 values:
349     * [year] [month] [day] */
350    if (date_list.size < 3)
351       goto error;
352 
353    /* Convert date string values */
354    entry->date.year  = string_to_unsigned(date_list.elems[0].data);
355    entry->date.month = string_to_unsigned(date_list.elems[1].data);
356    entry->date.day   = string_to_unsigned(date_list.elems[2].data);
357 
358    /* Clean up */
359    string_list_deinitialize(&date_list);
360 
361    return true;
362 
363 error:
364    string_list_deinitialize(&date_list);
365 
366    return false;
367 }
368 
369 /* Parses crc string and adds value to
370  * specified core updater list entry */
371 static bool core_updater_list_set_crc(
372       core_updater_list_entry_t *entry, const char *crc_str)
373 {
374    uint32_t crc;
375 
376    if (!entry || string_is_empty(crc_str))
377       return false;
378 
379    crc = (uint32_t)string_hex_to_unsigned(crc_str);
380 
381    if (crc == 0)
382       return false;
383 
384    entry->crc = crc;
385 
386    return true;
387 }
388 
389 /* Parses core filename string and adds all
390  * associated paths to the specified core
391  * updater list entry */
392 static bool core_updater_list_set_paths(
393       core_updater_list_entry_t *entry,
394       const char *path_dir_libretro,
395       const char *path_libretro_info,
396       const char *network_buildbot_url,
397       const char *filename_str,
398       enum core_updater_list_type list_type)
399 {
400    char *last_underscore                  = NULL;
401    char *tmp_url                          = NULL;
402    bool is_archive                        = true;
403    /* Can't resolve symlinks when dealing with cores
404     * installed via play feature delivery, because the
405     * source files have non-standard file names (which
406     * will not be recognised by regular core handling
407     * routines) */
408    bool resolve_symlinks                  = (list_type != CORE_UPDATER_LIST_TYPE_PFD);
409    char remote_core_path[PATH_MAX_LENGTH];
410    char local_core_path[PATH_MAX_LENGTH];
411    char local_info_path[PATH_MAX_LENGTH];
412 
413    remote_core_path[0] = '\0';
414    local_core_path[0]  = '\0';
415    local_info_path[0]  = '\0';
416 
417    if (!entry ||
418        string_is_empty(filename_str) ||
419        string_is_empty(path_dir_libretro) ||
420        string_is_empty(path_libretro_info))
421       return false;
422 
423    /* Only buildbot cores require the buildbot URL */
424    if ((list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT) &&
425        string_is_empty(network_buildbot_url))
426       return false;
427 
428    /* Check whether remote file is an archive */
429    is_archive = path_is_compressed_file(filename_str);
430 
431    /* remote_filename */
432    if (entry->remote_filename)
433    {
434       free(entry->remote_filename);
435       entry->remote_filename = NULL;
436    }
437 
438    entry->remote_filename = strdup(filename_str);
439 
440    /* remote_core_path
441     * > Leave blank if this is not a buildbot core */
442    if (list_type == CORE_UPDATER_LIST_TYPE_BUILDBOT)
443    {
444       fill_pathname_join(
445             remote_core_path,
446             network_buildbot_url,
447             filename_str,
448             sizeof(remote_core_path));
449 
450       /* > Apply proper URL encoding (messy...) */
451       tmp_url = strdup(remote_core_path);
452       remote_core_path[0] = '\0';
453       net_http_urlencode_full(
454             remote_core_path, tmp_url, sizeof(remote_core_path));
455       if (tmp_url)
456          free(tmp_url);
457    }
458 
459    if (entry->remote_core_path)
460    {
461       free(entry->remote_core_path);
462       entry->remote_core_path = NULL;
463    }
464 
465    entry->remote_core_path = strdup(remote_core_path);
466 
467    /* local_core_path */
468    fill_pathname_join(
469          local_core_path,
470          path_dir_libretro,
471          filename_str,
472          sizeof(local_core_path));
473 
474    if (is_archive)
475       path_remove_extension(local_core_path);
476 
477    path_resolve_realpath(local_core_path, sizeof(local_core_path),
478          resolve_symlinks);
479 
480    if (entry->local_core_path)
481    {
482       free(entry->local_core_path);
483       entry->local_core_path = NULL;
484    }
485 
486    entry->local_core_path = strdup(local_core_path);
487 
488    /* local_info_path */
489    fill_pathname_join_noext(
490          local_info_path,
491          path_libretro_info,
492          filename_str,
493          sizeof(local_info_path));
494 
495    if (is_archive)
496       path_remove_extension(local_info_path);
497 
498    /* > Remove any non-standard core filename
499     *   additions (i.e. info files end with
500     *   '_libretro' but core files may have
501     *   a platform specific addendum,
502     *   e.g. '_android')*/
503    last_underscore = (char*)strrchr(local_info_path, '_');
504 
505    if (!string_is_empty(last_underscore))
506       if (!string_is_equal(last_underscore, "_libretro"))
507          *last_underscore = '\0';
508 
509    /* > Add proper file extension */
510    strlcat(
511          local_info_path,
512          FILE_PATH_CORE_INFO_EXTENSION,
513          sizeof(local_info_path));
514 
515    if (entry->local_info_path)
516    {
517       free(entry->local_info_path);
518       entry->local_info_path = NULL;
519    }
520 
521    entry->local_info_path = strdup(local_info_path);
522 
523    return true;
524 }
525 
526 /* Reads info file associated with core and
527  * adds relevant information to updater list
528  * entry */
529 static bool core_updater_list_set_core_info(
530       core_updater_list_entry_t *entry,
531       const char *local_info_path,
532       const char *filename_str)
533 {
534    core_updater_info_t *core_info = NULL;
535 
536    if (!entry ||
537        string_is_empty(local_info_path) ||
538        string_is_empty(filename_str))
539       return false;
540 
541    /* Clear any existing core info */
542    if (entry->display_name)
543    {
544       free(entry->display_name);
545       entry->display_name = NULL;
546    }
547 
548    if (entry->description)
549    {
550       free(entry->description);
551       entry->description = NULL;
552    }
553 
554    if (entry->licenses_list)
555    {
556       /* Note: We can safely leave this as NULL if
557        * the core info file is invalid */
558       string_list_free(entry->licenses_list);
559       entry->licenses_list = NULL;
560    }
561 
562    entry->is_experimental = false;
563 
564    /* Read core info file
565     * > Note: It's a bit rubbish that we have to
566     *   read the actual core info files here...
567     *   Would be better to cache this globally
568     *   (at present, we only cache info for
569     *    *installed* cores...) */
570    core_info = core_info_get_core_updater_info(local_info_path);
571 
572    if (core_info)
573    {
574       /* display_name + is_experimental */
575       if (!string_is_empty(core_info->display_name))
576       {
577          entry->display_name    = strdup(core_info->display_name);
578          entry->is_experimental = core_info->is_experimental;
579       }
580       else
581       {
582          /* If display name is blank, use core filename and
583           * assume core is experimental (i.e. all 'fit for consumption'
584           * cores must have a valid/complete core info file) */
585          entry->display_name    = strdup(filename_str);
586          entry->is_experimental = true;
587       }
588 
589       /* description */
590       if (!string_is_empty(core_info->description))
591          entry->description     = strdup(core_info->description);
592       else
593          entry->description     = strdup("");
594 
595       /* licenses_list */
596       if (!string_is_empty(core_info->licenses))
597          entry->licenses_list   = string_split(core_info->licenses, "|");
598 
599       /* Clean up */
600       core_info_free_core_updater_info(core_info);
601    }
602    else
603    {
604       /* If info file is missing, use core filename and
605        * assume core is experimental (i.e. all 'fit for consumption'
606        * cores must have a valid/complete core info file) */
607       entry->display_name       = strdup(filename_str);
608       entry->is_experimental    = true;
609       entry->description        = strdup("");
610    }
611 
612    return true;
613 }
614 
615 /* Adds entry to the end of the specified core
616  * updater list
617  * NOTE: Entry string values are passed by
618  * reference - *do not free the entry passed
619  * to this function* */
620 static bool core_updater_list_push_entry(
621       core_updater_list_t *core_list, core_updater_list_entry_t *entry)
622 {
623    core_updater_list_entry_t *list_entry = NULL;
624    size_t num_entries;
625 
626    if (!core_list || !entry)
627       return false;
628 
629    /* Get current number of list entries */
630    num_entries = RBUF_LEN(core_list->entries);
631 
632    /* Attempt to allocate memory for new entry */
633    if (!RBUF_TRYFIT(core_list->entries, num_entries + 1))
634       return false;
635 
636    /* Allocation successful - increment array size */
637    RBUF_RESIZE(core_list->entries, num_entries + 1);
638 
639    /* Get handle of new entry at end of list, and
640     * zero-initialise members */
641    list_entry = &core_list->entries[num_entries];
642    memset(list_entry, 0, sizeof(*list_entry));
643 
644    /* Assign paths */
645    list_entry->remote_filename  = entry->remote_filename;
646    list_entry->remote_core_path = entry->remote_core_path;
647    list_entry->local_core_path  = entry->local_core_path;
648    list_entry->local_info_path  = entry->local_info_path;
649 
650    /* Assign core info */
651    list_entry->display_name     = entry->display_name;
652    list_entry->description      = entry->description;
653    list_entry->licenses_list    = entry->licenses_list;
654    list_entry->is_experimental  = entry->is_experimental;
655 
656    /* Copy crc */
657    list_entry->crc              = entry->crc;
658 
659    /* Copy date */
660    memcpy(&list_entry->date, &entry->date, sizeof(core_updater_list_date_t));
661 
662    return true;
663 }
664 
665 /* Parses the contents of a single buildbot
666  * core listing and adds it to the specified
667  * core updater list */
668 static void core_updater_list_add_entry(
669       core_updater_list_t *core_list,
670       const char *path_dir_libretro,
671       const char *path_libretro_info,
672       const char *network_buildbot_url,
673       struct string_list *network_core_entry_list)
674 {
675    const char *date_str                          = NULL;
676    const char *crc_str                           = NULL;
677    const char *filename_str                      = NULL;
678    const core_updater_list_entry_t *search_entry = NULL;
679    core_updater_list_entry_t entry               = {0};
680 
681    if (!core_list || !network_core_entry_list)
682       goto error;
683 
684    /* > Listings must have 3 entries:
685     *   [date] [crc] [filename] */
686    if (network_core_entry_list->size < 3)
687       goto error;
688 
689    /* Get handles of the individual listing strings */
690    date_str     = network_core_entry_list->elems[0].data;
691    crc_str      = network_core_entry_list->elems[1].data;
692    filename_str = network_core_entry_list->elems[2].data;
693 
694    if (string_is_empty(date_str) ||
695        string_is_empty(crc_str) ||
696        string_is_empty(filename_str))
697       goto error;
698 
699    /* Check whether core file is already included
700     * in the list (this is *not* an error condition,
701     * it just means we can skip the current listing) */
702    if (core_updater_list_get_filename(core_list,
703          filename_str, &search_entry))
704       goto error;
705 
706    /* Parse individual listing strings */
707    if (!core_updater_list_set_date(&entry, date_str))
708       goto error;
709 
710    if (!core_updater_list_set_crc(&entry, crc_str))
711       goto error;
712 
713    if (!core_updater_list_set_paths(
714             &entry,
715             path_dir_libretro,
716             path_libretro_info,
717             network_buildbot_url,
718             filename_str,
719             CORE_UPDATER_LIST_TYPE_BUILDBOT))
720       goto error;
721 
722    if (!core_updater_list_set_core_info(
723          &entry,
724          entry.local_info_path,
725          filename_str))
726       goto error;
727 
728    /* Add entry to list */
729    if (!core_updater_list_push_entry(core_list, &entry))
730       goto error;
731 
732    return;
733 
734 error:
735    /* This is not a *fatal* error - it just
736     * means one of the following:
737     * - The current line of entry text received
738     *   from the buildbot is broken somehow
739     *   (could be the case that the network buffer
740     *    wasn't large enough to cache the entire
741     *    string, so the last line was truncated)
742     * - We had insufficient memory to allocate a new
743     *   entry in the core updater list
744     * In either case, the current entry is discarded
745     * and we move on to the next one
746     * (network transfers are fishy business, so we
747     * choose to ignore this sort of error - don't
748     * want the whole fetch to fail because of a
749     * trivial glitch...) */
750    core_updater_list_free_entry(&entry);
751 }
752 
753 /* Core updater list qsort helper function */
754 static int core_updater_list_qsort_func(
755       const core_updater_list_entry_t *a, const core_updater_list_entry_t *b)
756 {
757    if (!a || !b)
758       return 0;
759 
760    if (string_is_empty(a->display_name) || string_is_empty(b->display_name))
761       return 0;
762 
763    return strcasecmp(a->display_name, b->display_name);
764 }
765 
766 /* Sorts core updater list into alphabetical order */
767 static void core_updater_list_qsort(core_updater_list_t *core_list)
768 {
769    size_t num_entries;
770 
771    if (!core_list)
772       return;
773 
774    num_entries = RBUF_LEN(core_list->entries);
775 
776    if (num_entries < 2)
777       return;
778 
779    qsort(
780          core_list->entries, num_entries,
781          sizeof(core_updater_list_entry_t),
782          (int (*)(const void *, const void *))
783                core_updater_list_qsort_func);
784 }
785 
786 /* Reads the contents of a buildbot core list
787  * network request into the specified
788  * core_updater_list_t object.
789  * Returns false in the event of an error. */
790 bool core_updater_list_parse_network_data(
791       core_updater_list_t *core_list,
792       const char *path_dir_libretro,
793       const char *path_libretro_info,
794       const char *network_buildbot_url,
795       const char *data, size_t len)
796 {
797    size_t i;
798    char *data_buf                       = NULL;
799    struct string_list network_core_list = {0};
800 
801    /* Sanity check */
802    if (!core_list || string_is_empty(data) || (len < 1))
803       goto error;
804 
805    /* We're populating a list 'from scratch' - remove
806     * any existing entries */
807    core_updater_list_reset(core_list);
808 
809    /* Input data string is not terminated - have
810     * to copy it to a temporary buffer... */
811    data_buf = (char*)malloc((len + 1) * sizeof(char));
812 
813    if (!data_buf)
814       goto error;
815 
816    memcpy(data_buf, data, len * sizeof(char));
817    data_buf[len] = '\0';
818 
819    /* Split network listing request into lines */
820    string_list_initialize(&network_core_list);
821    if (!string_split_noalloc(&network_core_list, data_buf, "\n"))
822       goto error;
823 
824    if (network_core_list.size < 1)
825       goto error;
826 
827    /* Temporary data buffer is no longer required */
828    free(data_buf);
829    data_buf = NULL;
830 
831    /* Loop over lines */
832    for (i = 0; i < network_core_list.size; i++)
833    {
834       struct string_list network_core_entry_list  = {0};
835       const char *line = network_core_list.elems[i].data;
836 
837       if (string_is_empty(line))
838          continue;
839 
840       string_list_initialize(&network_core_entry_list);
841       /* Split line into listings info components */
842       string_split_noalloc(&network_core_entry_list, line, " ");
843 
844       /* Parse listings info and add to core updater
845        * list */
846       core_updater_list_add_entry(
847             core_list,
848             path_dir_libretro,
849             path_libretro_info,
850             network_buildbot_url,
851             &network_core_entry_list);
852 
853       /* Clean up */
854       string_list_deinitialize(&network_core_entry_list);
855    }
856 
857    /* Sanity check */
858    if (RBUF_LEN(core_list->entries) < 1)
859       goto error;
860 
861    /* Clean up */
862    string_list_deinitialize(&network_core_list);
863 
864    /* Sort completed list */
865    core_updater_list_qsort(core_list);
866 
867    /* Set list type */
868    core_list->type = CORE_UPDATER_LIST_TYPE_BUILDBOT;
869 
870    return true;
871 
872 error:
873    string_list_deinitialize(&network_core_list);
874 
875    if (data_buf)
876       free(data_buf);
877 
878    return false;
879 }
880 
881 /* Parses a single play feature delivery core
882  * listing and adds it to the specified core
883  * updater list */
884 static void core_updater_list_add_pfd_entry(
885       core_updater_list_t *core_list,
886       const char *path_dir_libretro,
887       const char *path_libretro_info,
888       const char *filename_str)
889 {
890    const core_updater_list_entry_t *search_entry = NULL;
891    core_updater_list_entry_t entry               = {0};
892 
893    if (!core_list || string_is_empty(filename_str))
894       goto error;
895 
896    /* Check whether core file is already included
897     * in the list (this is *not* an error condition,
898     * it just means we can skip the current listing) */
899    if (core_updater_list_get_filename(core_list,
900          filename_str, &search_entry))
901       goto error;
902 
903    /* Note: Play feature delivery cores have no
904     * timestamp or CRC info - leave these fields
905     * zero initialised */
906 
907    /* Populate entry fields */
908    if (!core_updater_list_set_paths(
909             &entry,
910             path_dir_libretro,
911             path_libretro_info,
912             NULL,
913             filename_str,
914             CORE_UPDATER_LIST_TYPE_PFD))
915       goto error;
916 
917    if (!core_updater_list_set_core_info(
918          &entry,
919          entry.local_info_path,
920          filename_str))
921       goto error;
922 
923    /* Add entry to list */
924    if (!core_updater_list_push_entry(core_list, &entry))
925       goto error;
926 
927    return;
928 
929 error:
930    /* This is not a *fatal* error - it just
931     * means one of the following:
932     * - The core listing entry obtained from the
933     *   play feature delivery interface is broken
934     *   somehow
935     * - We had insufficient memory to allocate a new
936     *   entry in the core updater list
937     * In either case, the current entry is discarded
938     * and we move on to the next one */
939    core_updater_list_free_entry(&entry);
940 }
941 
942 /* Reads the list of cores currently available
943  * via play feature delivery (PFD) into the
944  * specified core_updater_list_t object.
945  * Returns false in the event of an error. */
946 bool core_updater_list_parse_pfd_data(
947       core_updater_list_t *core_list,
948       const char *path_dir_libretro,
949       const char *path_libretro_info,
950       const struct string_list *pfd_cores)
951 {
952    size_t i;
953 
954    /* Sanity check */
955    if (!core_list || !pfd_cores || (pfd_cores->size < 1))
956       return false;
957 
958    /* We're populating a list 'from scratch' - remove
959     * any existing entries */
960    core_updater_list_reset(core_list);
961 
962    /* Loop over play feature delivery core list */
963    for (i = 0; i < pfd_cores->size; i++)
964    {
965       const char *filename_str = pfd_cores->elems[i].data;
966 
967       if (string_is_empty(filename_str))
968          continue;
969 
970       /* Parse core file name and add to core
971        * updater list */
972       core_updater_list_add_pfd_entry(
973             core_list,
974             path_dir_libretro,
975             path_libretro_info,
976             filename_str);
977    }
978 
979    /* Sanity check */
980    if (RBUF_LEN(core_list->entries) < 1)
981       return false;
982 
983    /* Sort completed list */
984    core_updater_list_qsort(core_list);
985 
986    /* Set list type */
987    core_list->type = CORE_UPDATER_LIST_TYPE_PFD;
988 
989    return true;
990 }
991