The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
20#include "log.hpp"
21
22#if defined(__APPLE__)
23#include <TargetConditionals.h>
24#endif
25
26#ifdef __ANDROID__
27#include <SDL3/SDL_system.h>
28#endif
29
30#if !(defined(__APPLE__) && TARGET_OS_IPHONE)
31#include <curl/curl.h>
32#endif
33
34static lg::log_domain log_network("network");
35#define ERR_NW LOG_STREAM(err, log_network)
36#define DBG_NW LOG_STREAM(debug, log_network)
37
38namespace network
39{
40 static std::size_t write_callback(char* contents, std::size_t size, std::size_t nmemb, void* buffer)
41 {
42 std::size_t amount = size * nmemb;
43 static_cast<std::string*>(buffer)->append(contents, amount);
44 DBG_NW << "Downloaded " << amount << " bytes.";
45 return amount;
46 }
47
48 void gui_download([[maybe_unused]] const std::string& url, [[maybe_unused]] const std::string& local_path) {
49#if defined(__APPLE__) && TARGET_OS_IPHONE
50 gui2::show_message(_("Download unavailable"), _("Standalone file downloads are not currently supported on iOS."), gui2::dialogs::message::button_style::auto_close);
51 return;
52#endif
53
54 if(filesystem::file_exists(local_path)) {
55 const int res = gui2::show_message(_("Confirm overwrite"), _("Overwrite existing file?"), gui2::dialogs::message::yes_no_buttons);
56 if(res != gui2::retval::OK) {
57 return;
58 }
59 }
60 if (download(url, local_path)) {
61 gui2::show_message(_("Download complete"), _("The file has been downloaded."), gui2::dialogs::message::button_style::auto_close);
62 } else {
63 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);
64 }
65 }
66
67 bool download([[maybe_unused]] const std::string& url, [[maybe_unused]] const std::string& local_path)
68 {
69#if defined(__APPLE__) && TARGET_OS_IPHONE
70 ERR_NW << "Standalone file downloads are currently disabled for iOS builds.";
71 return false;
72#else
73 std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl(curl_easy_init(), curl_easy_cleanup);
74 std::string buffer;
75 // curl doesn't initialize the error buffer until version 7.60.0, which isn't currently available on all supported macOS versions
76 char error[CURL_ERROR_SIZE];
77 std::fill_n(error, CURL_ERROR_SIZE-1, ' ');
78 error[CURL_ERROR_SIZE-1] = '\0';
79
80 if(!curl) {
81 ERR_NW << "curl_easy_init failed initialization, unable to download file.";
82 return false;
83 }
84
85 CURLcode res;
86
87 if(
88#ifdef __ANDROID__
89 (res = curl_easy_setopt(curl.get(), CURLOPT_CAINFO, (game_config::path + "/certificates/cacert.pem").c_str()) ) != CURLE_OK ||
90#endif
91 (res = curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str())) != CURLE_OK ||
92 (res = curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback)) != CURLE_OK ||
93 (res = curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &buffer)) != CURLE_OK ||
94 (res = curl_easy_setopt(curl.get(), CURLOPT_ERRORBUFFER, error)) != CURLE_OK ||
95 (res = curl_easy_setopt(curl.get(), CURLOPT_FORBID_REUSE, 1L)) != CURLE_OK ||
96 (res = curl_easy_setopt(curl.get(), CURLOPT_FRESH_CONNECT, 1L)) != CURLE_OK ||
97 (res = curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1L)) != CURLE_OK ||
98 (res = curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT_MS, 5000L)) != CURLE_OK ||
99#if LIBCURL_VERSION_NUM >= 0x075500
100 (res = curl_easy_setopt(curl.get(), CURLOPT_PROTOCOLS_STR, "https")) != CURLE_OK
101#else
102 (res = curl_easy_setopt(curl.get(), CURLOPT_PROTOCOLS, CURLPROTO_HTTPS)) != CURLE_OK
103#endif
104 ) {
105 ERR_NW << "Error setting curl option: " << curl_easy_strerror(res);
106 return false;
107 }
108
109 res = curl_easy_perform(curl.get());
110 if(res != CURLE_OK) {
111 ERR_NW << "Error downloading file from url `" << url << "`.\n"
112 << "Short error: " << curl_easy_strerror(res) << "\n"
113 << "Long error: " << std::string(error);
114 return false;
115 }
116
117 try {
118 filesystem::write_file(local_path, buffer);
119 DBG_NW << "Wrote downloaded file to: " << local_path;
120 } catch(const filesystem::io_exception& e) {
121 ERR_NW << "io_exception writing downloaded data to file at: " << local_path << "\n" << e.what() << " : " << e.message;
122 return false;
123 }
124 return true;
125#endif
126 }
127}
@ yes_no_buttons
Shows a yes and no button.
Definition message.hpp:81
@ auto_close
Enables auto close.
Definition message.hpp:71
Declarations for File-IO.
static std::string _(const char *str)
Definition gettext.hpp:100
Standard logging facilities (interface).
#define DBG_NW
#define ERR_NW
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode=std::ios_base::binary)
Throws io_exception if an error occurs.
std::string path
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.
static std::size_t write_callback(char *contents, std::size_t size, std::size_t nmemb, void *buffer)
bool download(const std::string &url, const std::string &local_path)
static lg::log_domain log_network("network")
An exception object used when an IO error occurs.
#define e