download.cpp (6196B)
1 /* ReaPack: Package manager for REAPER 2 * Copyright (C) 2015-2025 Christian Fillion 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "download.hpp" 19 20 #include "buildinfo.hpp" 21 #include "filesystem.hpp" 22 #include "hash.hpp" 23 #include "reapack.hpp" 24 #include "win32.hpp" 25 26 #include <cassert> 27 28 #include <reaper_plugin_functions.h> 29 30 static const int DOWNLOAD_TIMEOUT = 15; 31 // to set the amount of concurrent downloads, change the size of 32 // the m_pool member in ThreadPool (thread.hpp) 33 34 static CURLSH *g_curlShare = nullptr; 35 static std::mutex g_curlMutex; 36 37 static void LockCurlMutex(CURL *, curl_lock_data, curl_lock_access, void *) 38 { 39 g_curlMutex.lock(); 40 } 41 42 static void UnlockCurlMutex(CURL *, curl_lock_data, curl_lock_access, void *) 43 { 44 g_curlMutex.unlock(); 45 } 46 47 void DownloadContext::GlobalInit() 48 { 49 curl_global_init(CURL_GLOBAL_DEFAULT); 50 51 g_curlShare = curl_share_init(); 52 assert(g_curlShare); 53 54 curl_share_setopt(g_curlShare, CURLSHOPT_LOCKFUNC, LockCurlMutex); 55 curl_share_setopt(g_curlShare, CURLSHOPT_UNLOCKFUNC, UnlockCurlMutex); 56 57 curl_share_setopt(g_curlShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); 58 curl_share_setopt(g_curlShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); 59 } 60 61 void DownloadContext::GlobalCleanup() 62 { 63 curl_share_cleanup(g_curlShare); 64 curl_global_cleanup(); 65 } 66 67 DownloadContext::DownloadContext() 68 { 69 m_curl = curl_easy_init(); 70 71 char userAgent[64]; 72 snprintf(userAgent, sizeof(userAgent), "ReaPack/%s REAPER/%s", 73 REAPACK_VERSION, GetAppVersion()); 74 75 curl_easy_setopt(m_curl, CURLOPT_USERAGENT, userAgent); 76 curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_LIMIT, 1); 77 curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_TIME, DOWNLOAD_TIMEOUT); 78 curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, DOWNLOAD_TIMEOUT); 79 curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, true); 80 curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, 5); 81 curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, ""); 82 curl_easy_setopt(m_curl, CURLOPT_FAILONERROR, true); 83 curl_easy_setopt(m_curl, CURLOPT_SHARE, g_curlShare); 84 curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, false); 85 } 86 87 DownloadContext::~DownloadContext() 88 { 89 curl_easy_cleanup(m_curl); 90 } 91 92 size_t Download::WriteData(char *data, size_t rawsize, size_t nmemb, void *ptr) 93 { 94 const size_t size = rawsize * nmemb; 95 96 static_cast<WriteContext *>(ptr)->write(data, size); 97 98 return size; 99 } 100 101 int Download::UpdateProgress(void *ptr, const double, const double, 102 const double, const double) 103 { 104 return static_cast<Download *>(ptr)->aborted(); 105 } 106 107 Download::Download(const std::string &url, const NetworkOpts &opts, const int flags) 108 : m_url(url), m_opts(opts), m_flags(flags) 109 { 110 } 111 112 void Download::setName(const std::string &name) 113 { 114 setSummary({ "Downloading", name }); 115 } 116 117 bool Download::run() 118 { 119 WriteContext write; 120 121 Hash::Algorithm algo; 122 if(!m_expectedChecksum.empty()) { 123 if(Hash::getAlgorithm(m_expectedChecksum, &algo)) 124 write.hash = std::make_unique<Hash>(algo); 125 else { 126 const std::string &error = String::format( 127 "Unsupported checksum: %s", m_expectedChecksum.c_str()); 128 setError({error, m_url}); 129 return false; 130 } 131 } 132 133 if(!(write.stream = openStream())) 134 return false; 135 136 thread_local DownloadContext ctx; 137 138 curl_easy_setopt(ctx, CURLOPT_URL, m_url.c_str()); 139 curl_easy_setopt(ctx, CURLOPT_PROXY, m_opts.proxy.c_str()); 140 curl_easy_setopt(ctx, CURLOPT_SSL_VERIFYPEER, m_opts.verifyPeer); 141 #ifdef __APPLE__ 142 curl_easy_setopt(ctx, CURLOPT_CAINFO, nullptr); 143 #endif 144 145 curl_easy_setopt(ctx, CURLOPT_PROGRESSFUNCTION, UpdateProgress); 146 curl_easy_setopt(ctx, CURLOPT_PROGRESSDATA, this); 147 148 curl_easy_setopt(ctx, CURLOPT_WRITEFUNCTION, WriteData); 149 curl_easy_setopt(ctx, CURLOPT_WRITEDATA, &write); 150 151 curl_slist *headers = nullptr; 152 if(has(Download::NoCacheFlag)) 153 headers = curl_slist_append(headers, "Cache-Control: no-cache"); 154 curl_easy_setopt(ctx, CURLOPT_HTTPHEADER, headers); 155 156 std::string errbuf = "No error message"; 157 errbuf.resize(CURL_ERROR_SIZE - 1, '\0'); 158 curl_easy_setopt(ctx, CURLOPT_ERRORBUFFER, errbuf.data()); 159 160 const CURLcode res = curl_easy_perform(ctx); 161 curl_slist_free_all(headers); 162 closeStream(); 163 164 if(res != CURLE_OK) { 165 #ifdef _WIN32 166 errbuf = Win32::ansi2utf8(errbuf); 167 #endif 168 169 const std::string &err = String::format( 170 "%s (%d): %s", curl_easy_strerror(res), res, errbuf.c_str()); 171 setError({err, m_url}); 172 return false; 173 } 174 else if(write.hash && write.hash->digest() != m_expectedChecksum) { 175 const std::string &err = String::format( 176 "Checksum mismatch.\nExpected: %s\nActual: %s", 177 m_expectedChecksum.c_str(), write.hash->digest().c_str() 178 ); 179 setError({err, m_url}); 180 return false; 181 } 182 183 return true; 184 } 185 186 void Download::WriteContext::write(const char *data, const size_t len) 187 { 188 stream->write(data, len); 189 190 if(hash) 191 hash->addData(data, len); 192 } 193 194 MemoryDownload::MemoryDownload(const std::string &url, const NetworkOpts &opts, int flags) 195 : Download(url, opts, flags) 196 { 197 setName(url); 198 } 199 200 FileDownload::FileDownload(const Path &target, const std::string &url, 201 const NetworkOpts &opts, int flags) 202 : Download(url, opts, flags), m_path(target) 203 { 204 setName(target.join()); 205 } 206 207 bool FileDownload::save() 208 { 209 if(state() == Success) 210 return FS::rename(m_path); 211 else 212 return FS::remove(m_path.temp()); 213 } 214 215 std::ostream *FileDownload::openStream() 216 { 217 if(FS::open(m_stream, m_path.temp())) 218 return &m_stream; 219 else { 220 setError({FS::lastError(), m_path.temp().join()}); 221 return nullptr; 222 } 223 } 224 225 void FileDownload::closeStream() 226 { 227 m_stream.close(); 228 }