1 /***********************************************************************
2  Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17 
18 #include "fc_prehdrs.h"
19 
20 #ifdef FREECIV_HAVE_SYS_TYPES_H
21 #include <sys/types.h>
22 #endif
23 #ifdef HAVE_SYS_SOCKET_H
24 #include <sys/socket.h>
25 #endif
26 #include <unistd.h>
27 #include <errno.h>
28 
29 /* dependencies */
30 #include "cvercmp.h"
31 
32 /* utility */
33 #include "capability.h"
34 #include "fcintl.h"
35 #include "log.h"
36 #include "mem.h"
37 #include "netfile.h"
38 #include "registry.h"
39 
40 /* modinst */
41 #include "download.h"
42 
43 static const char *download_modpack_recursive(const char *URL,
44                                               const struct fcmp_params *fcmp,
45                                               dl_msg_callback mcb,
46                                               dl_pb_callback pbcb,
47                                               int recursion);
48 
49 /**************************************************************************
50   Message callback called by netfile module when downloading files.
51 **************************************************************************/
nf_cb(const char * msg,void * data)52 static void nf_cb(const char *msg, void *data)
53 {
54   dl_msg_callback mcb = (dl_msg_callback) data;
55 
56   if (mcb != NULL) {
57     mcb(msg);
58   }
59 }
60 
61 /**************************************************************************
62   Download modpack from a given URL
63 **************************************************************************/
download_modpack(const char * URL,const struct fcmp_params * fcmp,dl_msg_callback mcb,dl_pb_callback pbcb)64 const char *download_modpack(const char *URL,
65 			     const struct fcmp_params *fcmp,
66                              dl_msg_callback mcb,
67                              dl_pb_callback pbcb)
68 {
69   return download_modpack_recursive(URL, fcmp, mcb, pbcb, 0);
70 }
71 
72 /**************************************************************************
73   Download modpack and its recursive dependencies.
74 **************************************************************************/
download_modpack_recursive(const char * URL,const struct fcmp_params * fcmp,dl_msg_callback mcb,dl_pb_callback pbcb,int recursion)75 static const char *download_modpack_recursive(const char *URL,
76                                               const struct fcmp_params *fcmp,
77                                               dl_msg_callback mcb,
78                                               dl_pb_callback pbcb,
79                                               int recursion)
80 {
81   char local_dir[2048];
82   char local_name[2048];
83   int start_idx;
84   int filenbr, total_files;
85   struct section_file *control;
86   const char *control_capstr;
87   const char *baseURLpart;
88   enum modpack_type type;
89   const char *typestr;
90   const char *mpname;
91   const char *mpver;
92   char baseURL[2048];
93   char fileURL[2048];
94   const char *src_name;
95   bool partial_failure = FALSE;
96   int dep;
97   const char *dep_name;
98 
99   if (recursion > 5) {
100     return _("Recursive dependencies too deep");
101   }
102 
103   if (URL == NULL || URL[0] == '\0') {
104     return _("No URL given");
105   }
106 
107   if (strlen(URL) < strlen(MODPACK_SUFFIX)
108       || strcmp(URL + strlen(URL) - strlen(MODPACK_SUFFIX), MODPACK_SUFFIX)) {
109     return _("This does not look like modpack URL");
110   }
111 
112   for (start_idx = strlen(URL) - strlen(MODPACK_SUFFIX);
113        start_idx > 0 && URL[start_idx - 1] != '/';
114        start_idx--) {
115     /* Nothing */
116   }
117 
118   log_normal(_("Installing modpack %s from %s"), URL + start_idx, URL);
119 
120   if (fcmp->inst_prefix == NULL) {
121     return _("Cannot install to given directory hierarchy");
122   }
123 
124   if (mcb != NULL) {
125     char buf[2048];
126 
127     /* TRANS: %s is a filename with suffix '.modpack' */
128     fc_snprintf(buf, sizeof(buf), _("Downloading \"%s\" control file."), URL + start_idx);
129     mcb(buf);
130   }
131 
132   control = netfile_get_section_file(URL, nf_cb, mcb);
133 
134   if (control == NULL) {
135     return _("Failed to get and parse modpack control file");
136   }
137 
138   control_capstr = secfile_lookup_str(control, "info.options");
139   if (control_capstr == NULL) {
140     secfile_destroy(control);
141     return _("Modpack control file has no capability string");
142   }
143 
144   if (!has_capabilities(MODPACK_CAPSTR, control_capstr)) {
145     log_error("Incompatible control file:");
146     log_error("  control file options: %s", control_capstr);
147     log_error("  supported options:    %s", MODPACK_CAPSTR);
148 
149     secfile_destroy(control);
150 
151     return _("Modpack control file is incompatible");
152   }
153 
154   mpname = secfile_lookup_str(control, "info.name");
155   if (mpname == NULL) {
156     return _("Modpack name not defined in control file");
157   }
158   mpver = secfile_lookup_str(control, "info.version");
159   if (mpver  == NULL) {
160     return _("Modpack version not defined in control file");
161   }
162 
163   typestr = secfile_lookup_str(control, "info.type");
164   type = modpack_type_by_name(typestr, fc_strcasecmp);
165   if (!modpack_type_is_valid(type)) {
166     return _("Illegal modpack type");
167   }
168 
169   if (type == MPT_SCENARIO) {
170     fc_snprintf(local_dir, sizeof(local_dir),
171                 "%s" DIR_SEPARATOR "scenarios", fcmp->inst_prefix);
172   } else {
173     fc_snprintf(local_dir, sizeof(local_dir),
174                 "%s" DIR_SEPARATOR DATASUBDIR, fcmp->inst_prefix);
175   }
176 
177   baseURLpart = secfile_lookup_str(control, "info.baseURL");
178 
179   if (baseURLpart[0] == '.') {
180     char URLstart[start_idx];
181 
182     strncpy(URLstart, URL, start_idx - 1);
183     URLstart[start_idx - 1] = '\0';
184     fc_snprintf(baseURL, sizeof(baseURL), "%s%s",
185                 URLstart, baseURLpart + 1);
186   } else {
187     sz_strlcpy(baseURL, baseURLpart);
188   }
189 
190   dep = 0;
191   do {
192     dep_name = secfile_lookup_str_default(control, NULL,
193                                           "dependencies.list%d.modpack", dep);
194     if (dep_name != NULL) {
195       const char *dep_URL;
196       const char *inst_ver;
197       const char *dep_typestr;
198       enum modpack_type dep_type;
199       bool needed = TRUE;
200 
201       dep_URL = secfile_lookup_str_default(control, NULL,
202                                            "dependencies.list%d.URL", dep);
203 
204       if (dep_URL == NULL) {
205         return _("Dependency has no download URL");
206       }
207 
208       dep_typestr = secfile_lookup_str(control, "dependencies.list%d.type", dep);
209       dep_type = modpack_type_by_name(dep_typestr, fc_strcasecmp);
210       if (!modpack_type_is_valid(dep_type)) {
211         return _("Illegal dependency modpack type");
212       }
213 
214       inst_ver = get_installed_version(dep_name, type);
215 
216       if (inst_ver != NULL) {
217         const char *dep_ver;
218 
219         dep_ver = secfile_lookup_str_default(control, NULL,
220                                              "dependencies.list%d.version", dep);
221 
222         if (dep_ver != NULL && cvercmp_max(dep_ver, inst_ver)) {
223           needed = FALSE;
224         }
225       }
226 
227       if (needed) {
228         const char *msg;
229         char dep_URL_full[2048];
230 
231         log_debug("Dependency modpack \"%s\" needed.", dep_name);
232 
233         if (mcb != NULL) {
234           mcb(_("Download dependency modpack"));
235         }
236 
237         if (dep_URL[0] == '.') {
238           char URLstart[start_idx];
239 
240           strncpy(URLstart, URL, start_idx - 1);
241           URLstart[start_idx - 1] = '\0';
242           fc_snprintf(dep_URL_full, sizeof(dep_URL_full), "%s%s",
243                       URLstart, dep_URL + 1);
244         } else {
245           sz_strlcpy(dep_URL_full, dep_URL);
246         }
247 
248         msg = download_modpack_recursive(dep_URL_full, fcmp, mcb, pbcb, recursion + 1);
249 
250         if (msg != NULL) {
251           return msg;
252         }
253       }
254     }
255 
256     dep++;
257 
258   } while (dep_name != NULL);
259 
260 
261   total_files = 0;
262   do {
263     src_name = secfile_lookup_str_default(control, NULL,
264                                           "files.list%d.src", total_files);
265 
266     if (src_name != NULL) {
267       total_files++;
268     }
269   } while (src_name != NULL);
270 
271   if (pbcb != NULL) {
272     /* Control file already downloaded */
273     pbcb(1, total_files + 1);
274   }
275 
276   filenbr = 0;
277   for (filenbr = 0; filenbr < total_files; filenbr++) {
278     const char *dest_name;
279 
280 #ifndef DIR_SEPARATOR_IS_DEFAULT
281     char *dest_name_copy;
282 #else  /* DIR_SEPARATOR_IS_DEFAULT */
283 #define dest_name_copy dest_name
284 #endif /* DIR_SEPARATOR_IS_DEFAULT */
285 
286     int i;
287     bool illegal_filename = FALSE;
288 
289     src_name = secfile_lookup_str_default(control, NULL,
290                                           "files.list%d.src", filenbr);
291 
292     dest_name = secfile_lookup_str_default(control, NULL,
293                                            "files.list%d.dest", filenbr);
294 
295     if (dest_name == NULL || dest_name[0] == '\0') {
296       /* Missing dest name is ok, we just default to src_name */
297       dest_name = src_name;
298     }
299 
300 #ifndef DIR_SEPARATOR_IS_DEFAULT
301     dest_name_copy = fc_malloc(strlen(dest_name) + 1);
302 #endif /* DIR_SEPARATOR_IS_DEFAULT */
303 
304     for (i = 0; dest_name[i] != '\0'; i++) {
305       if (dest_name[i] == '.' && dest_name[i+1] == '.') {
306         if (mcb != NULL) {
307           char buf[2048];
308 
309           fc_snprintf(buf, sizeof(buf), _("Illegal path for %s"),
310                       dest_name);
311           mcb(buf);
312         }
313         partial_failure = TRUE;
314         illegal_filename = TRUE;
315       }
316 
317 #ifndef DIR_SEPARATOR_IS_DEFAULT
318       if (dest_name[i] == '/') {
319         dest_name_copy[i] = DIR_SEPARATOR_CHAR;
320       } else {
321         dest_name_copy[i] = dest_name[i];
322       }
323 #endif /* DIR_SEPARATOR_IS_DEFAULT */
324     }
325 
326 #ifndef DIR_SEPARATOR_IS_DEFAULT
327     dest_name_copy[i] = '\0';
328 #endif /* DIR_SEPARATOR_IS_DEFAULT */
329 
330     if (!illegal_filename) {
331       fc_snprintf(local_name, sizeof(local_name),
332                   "%s" DIR_SEPARATOR "%s", local_dir, dest_name_copy);
333 
334 #ifndef DIR_SEPARATOR_IS_DEFAULT
335       free(dest_name_copy);
336 #endif /* DIR_SEPARATOR_IS_DEFAULT */
337 
338       for (i = strlen(local_name) - 1 ; local_name[i] != DIR_SEPARATOR_CHAR ; i--) {
339         /* Nothing */
340       }
341       local_name[i] = '\0';
342       log_debug("Create directory \"%s\"", local_name);
343       if (!make_dir(local_name)) {
344         secfile_destroy(control);
345         return _("Cannot create required directories");
346       }
347       local_name[i] = DIR_SEPARATOR_CHAR;
348 
349       if (mcb != NULL) {
350         char buf[2048];
351 
352         fc_snprintf(buf, sizeof(buf), _("Downloading %s"), src_name);
353         mcb(buf);
354       }
355 
356       fc_snprintf(fileURL, sizeof(fileURL), "%s/%s", baseURL, src_name);
357       log_debug("Download \"%s\" as \"%s\".", fileURL, local_name);
358       if (!netfile_download_file(fileURL, local_name, nf_cb, mcb)) {
359         if (mcb != NULL) {
360           char buf[2048];
361 
362           fc_snprintf(buf, sizeof(buf), _("Failed to download %s"),
363                       src_name);
364           mcb(buf);
365         }
366         partial_failure = TRUE;
367       }
368     } else {
369 #ifndef DIR_SEPARATOR_IS_DEFAULT
370       free(dest_name_copy);
371 #endif /* DIR_SEPARATOR_IS_DEFAULT */
372     }
373 
374     if (pbcb != NULL) {
375       /* Count download of control file also */
376       pbcb(filenbr + 2, total_files + 1);
377     }
378   }
379 
380   if (partial_failure) {
381     secfile_destroy(control);
382 
383     return _("Some parts of the modpack failed to install.");
384   }
385 
386   update_install_info_lists(mpname, type, mpver);
387 
388   secfile_destroy(control);
389 
390   return NULL;
391 }
392 
393 /**************************************************************************
394   Download modpack list
395 **************************************************************************/
download_modpack_list(const struct fcmp_params * fcmp,modpack_list_setup_cb cb,dl_msg_callback mcb)396 const char *download_modpack_list(const struct fcmp_params *fcmp,
397                                   modpack_list_setup_cb cb,
398                                   dl_msg_callback mcb)
399 {
400   struct section_file *list_file;
401   const char *list_capstr;
402   int modpack_count;
403   const char *msg;
404   const char *mp_name;
405   int start_idx;
406 
407   list_file = netfile_get_section_file(fcmp->list_url, nf_cb, mcb);
408 
409   if (list_file == NULL) {
410     return _("Cannot fetch and parse modpack list");
411   }
412 
413   for (start_idx = strlen(fcmp->list_url);
414        start_idx > 0 && fcmp->list_url[start_idx - 1] != '/';
415        start_idx--) {
416     /* Nothing */
417   }
418 
419   list_capstr = secfile_lookup_str(list_file, "info.options");
420   if (list_capstr == NULL) {
421     secfile_destroy(list_file);
422     return _("Modpack list has no capability string");
423   }
424 
425   if (!has_capabilities(MODLIST_CAPSTR, list_capstr)) {
426     log_error("Incompatible modpack list file:");
427     log_error("  list file options: %s", list_capstr);
428     log_error("  supported options: %s", MODLIST_CAPSTR);
429 
430     secfile_destroy(list_file);
431 
432     return _("Modpack list is incompatible");
433   }
434 
435   msg = secfile_lookup_str_default(list_file, NULL, "info.message");
436 
437   if (msg != NULL) {
438     mcb(msg);
439   }
440 
441   modpack_count = 0;
442   do {
443     const char *mpURL;
444     const char *mpver;
445     const char *mplic;
446     const char *mp_type_str;
447     const char *mp_subtype;
448     const char *mp_notes;
449 
450     mp_name = secfile_lookup_str_default(list_file, NULL,
451                                          "modpacks.list%d.name", modpack_count);
452     mpver = secfile_lookup_str_default(list_file, NULL,
453                                        "modpacks.list%d.version",
454                                        modpack_count);
455     mplic = secfile_lookup_str_default(list_file, NULL,
456                                        "modpacks.list%d.license",
457                                        modpack_count);
458     mp_type_str = secfile_lookup_str_default(list_file, NULL,
459                                              "modpacks.list%d.type",
460                                              modpack_count);
461     mp_subtype = secfile_lookup_str_default(list_file, NULL,
462                                             "modpacks.list%d.subtype",
463                                             modpack_count);
464     mpURL = secfile_lookup_str_default(list_file, NULL,
465                                        "modpacks.list%d.URL", modpack_count);
466     mp_notes = secfile_lookup_str_default(list_file, NULL,
467                                           "modpacks.list%d.notes", modpack_count);
468 
469     if (mp_name != NULL && mpURL != NULL) {
470       char mpURL_full[2048];
471       enum modpack_type type = modpack_type_by_name(mp_type_str, fc_strcasecmp);
472 
473       if (!modpack_type_is_valid(type)) {
474         log_error("Illegal modpack type \"%s\"", mp_type_str ? mp_type_str : "NULL");
475       }
476       if (mpver == NULL) {
477         mpver = "-";
478       }
479       if (mp_subtype == NULL) {
480         mp_subtype = "-";
481       }
482 
483       if (mpURL[0] == '.') {
484         char URLstart[start_idx];
485 
486         strncpy(URLstart, fcmp->list_url, start_idx - 1);
487         URLstart[start_idx - 1] = '\0';
488         fc_snprintf(mpURL_full, sizeof(mpURL_full), "%s%s",
489                     URLstart, mpURL + 1);
490       } else {
491         sz_strlcpy(mpURL_full, mpURL);
492       }
493 
494       cb(mp_name, mpURL_full, mpver, mplic, type, _(mp_subtype), mp_notes);
495     }
496     modpack_count++;
497   } while (mp_name != NULL);
498 
499   return NULL;
500 }
501