The Battle for Wesnoth  1.19.13+dev
network_download_file.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include "filesystem.hpp"
18 #include "gettext.hpp"
19 #include "gui/dialogs/message.hpp"
20 #include "log.hpp"
21 
22 #ifdef __ANDROID__
23 #include <SDL2/SDL_system.h>
24 #endif
25 
26 #include <curl/curl.h>
27 
28 static lg::log_domain log_network("network");
29 #define ERR_NW LOG_STREAM(err, log_network)
30 #define DBG_NW LOG_STREAM(debug, log_network)
31 
32 namespace network
33 {
34  static size_t write_callback(char* contents, size_t size, size_t nmemb, void* buffer)
35  {
36  size_t amount = size * nmemb;
37  static_cast<std::string*>(buffer)->append(contents, amount);
38  DBG_NW << "Downloaded " << amount << " bytes.";
39  return amount;
40  }
41 
42  void gui_download(const std::string& url, const std::string& local_path) {
43  if(filesystem::file_exists(local_path)) {
44  const int res = gui2::show_message(_("Confirm overwrite"), _("Overwrite existing file?"), gui2::dialogs::message::yes_no_buttons);
45  if(res != gui2::retval::OK) {
46  return;
47  }
48  }
49  if (download(url, local_path)) {
50  gui2::show_message(_("Download complete"), _("The file has been downloaded."), gui2::dialogs::message::button_style::auto_close);
51  } else {
52  gui2::show_message(_("Download error"), _("An error occurred when downloading the file. Check the game logs for more information."), gui2::dialogs::message::button_style::auto_close);
53  }
54  }
55 
56  bool download(const std::string& url, const std::string& local_path)
57  {
58  std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl(curl_easy_init(), curl_easy_cleanup);
59  std::string buffer;
60  // curl doesn't initialize the error buffer until version 7.60.0, which isn't currently available on all supported macOS versions
61  char error[CURL_ERROR_SIZE];
62  std::fill_n(error, CURL_ERROR_SIZE-1, ' ');
63  error[CURL_ERROR_SIZE-1] = '\0';
64 
65  if(!curl) {
66  ERR_NW << "curl_easy_init failed initialization, unable to download file.";
67  return false;
68  }
69 
70  CURLcode res;
71 
72  if(
73 #ifdef __ANDROID__
74  (res = curl_easy_setopt(curl.get(), CURLOPT_CAINFO, (game_config::path + "/certificates/cacert.pem").c_str()) ) != CURLE_OK ||
75 #endif
76  (res = curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str())) != CURLE_OK ||
77  (res = curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback)) != CURLE_OK ||
78  (res = curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &buffer)) != CURLE_OK ||
79  (res = curl_easy_setopt(curl.get(), CURLOPT_ERRORBUFFER, error)) != CURLE_OK ||
80  (res = curl_easy_setopt(curl.get(), CURLOPT_FORBID_REUSE, 1L)) != CURLE_OK ||
81  (res = curl_easy_setopt(curl.get(), CURLOPT_FRESH_CONNECT, 1L)) != CURLE_OK ||
82  (res = curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1L)) != CURLE_OK ||
83  (res = curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT_MS, 5000L)) != CURLE_OK ||
84 #if LIBCURL_VERSION_NUM >= 0x075500
85  (res = curl_easy_setopt(curl.get(), CURLOPT_PROTOCOLS_STR, "https")) != CURLE_OK
86 #else
87  (res = curl_easy_setopt(curl.get(), CURLOPT_PROTOCOLS, CURLPROTO_HTTPS)) != CURLE_OK
88 #endif
89  ) {
90  ERR_NW << "Error setting curl option: " << curl_easy_strerror(res);
91  return false;
92  }
93 
94  res = curl_easy_perform(curl.get());
95  if(res != CURLE_OK) {
96  ERR_NW << "Error downloading file from url `" << url << "`.\n"
97  << "Short error: " << curl_easy_strerror(res) << "\n"
98  << "Long error: " << std::string(error);
99  return false;
100  }
101 
102  try {
103  filesystem::write_file(local_path, buffer);
104  DBG_NW << "Wrote downloaded file to: " << local_path;
105  } catch(const filesystem::io_exception& e) {
106  ERR_NW << "io_exception writing downloaded data to file at: " << local_path << "\n" << e.what() << " : " << e.message;
107  return false;
108  }
109  return true;
110  }
111 }
@ yes_no_buttons
Shows a yes and no button.
Definition: message.hpp:81
Declarations for File-IO.
static std::string _(const char *str)
Definition: gettext.hpp:97
Standard logging facilities (interface).
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:341
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
std::string path
Definition: filesystem.cpp:106
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
High level network layer for config object transport.
void gui_download(const std::string &url, const std::string &local_path)
Initiates a standalone download of a single file from an HTTPS URL.
bool download(const std::string &url, const std::string &local_path)
static size_t write_callback(char *contents, size_t size, size_t nmemb, void *buffer)
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
#define ERR_NW
static lg::log_domain log_network("network")
#define DBG_NW
An exception object used when an IO error occurs.
Definition: filesystem.hpp:67
#define e