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