00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 #include "addon/addon_manager.hpp"
00018
00019 #include <config.h>
00020 #include <version.h>
00021
00022 #include <algorithm>
00023 #include <memory>
00024 #include <physfs.h>
00025 #include <sstream>
00026 #include <stdexcept>
00027 #include <sys/stat.h>
00028
00029 #ifdef HAVE_LIBCURL
00030 # include <curl/curl.h>
00031 # include <curl/easy.h>
00032 #endif
00033
00034 #include "addon/addon.hpp"
00035 #include "lisp/list_iterator.hpp"
00036 #include "lisp/parser.hpp"
00037 #include "util/reader.hpp"
00038 #include "util/writer.hpp"
00039 #include "util/log.hpp"
00040
00041 #ifdef HAVE_LIBCURL
00042 namespace {
00043
00044 size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr)
00045 {
00046 std::string& s = *static_cast<std::string*>(string_ptr);
00047 std::string buf(static_cast<char*>(ptr), size * nmemb);
00048 s += buf;
00049 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
00050 return size * nmemb;
00051 }
00052
00053 size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p)
00054 {
00055 PHYSFS_file* f = static_cast<PHYSFS_file*>(f_p);
00056 PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
00057 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
00058 return size * written;
00059 }
00060
00061 }
00062 #endif
00063
00064 AddonManager&
00065 AddonManager::get_instance()
00066 {
00067 static AddonManager instance;
00068 return instance;
00069 }
00070
00071 AddonManager::AddonManager() :
00072 addons(),
00073 ignored_addon_filenames()
00074 {
00075 #ifdef HAVE_LIBCURL
00076 curl_global_init(CURL_GLOBAL_ALL);
00077 #endif
00078 }
00079
00080 AddonManager::~AddonManager()
00081 {
00082 #ifdef HAVE_LIBCURL
00083 curl_global_cleanup();
00084 #endif
00085
00086 for (std::vector<Addon*>::iterator i = addons.begin(); i != addons.end(); i++) delete *i;
00087 }
00088
00089 std::vector<Addon*>
00090 AddonManager::get_addons()
00091 {
00092
00093
00094
00095
00096
00097
00098 return addons;
00099 }
00100
00101 void
00102 AddonManager::check_online()
00103 {
00104 #ifdef HAVE_LIBCURL
00105 char error_buffer[CURL_ERROR_SIZE+1];
00106
00107 const char* baseUrl = "http://supertux.lethargik.org/addons/index.nfo";
00108 std::string addoninfos = "";
00109
00110 CURL *curl_handle;
00111 curl_handle = curl_easy_init();
00112 curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl);
00113 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
00114 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append);
00115 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos);
00116 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
00117 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
00118 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
00119 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
00120 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
00121 CURLcode result = curl_easy_perform(curl_handle);
00122 curl_easy_cleanup(curl_handle);
00123
00124 if (result != CURLE_OK) {
00125 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
00126 throw std::runtime_error("Downloading Add-on list failed: " + why);
00127 }
00128
00129 try {
00130 lisp::Parser parser;
00131 std::stringstream addoninfos_stream(addoninfos);
00132 const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons");
00133
00134 const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons");
00135 if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list");
00136
00137 lisp::ListIterator iter(addons_lisp);
00138 while(iter.next())
00139 {
00140 const std::string& token = iter.item();
00141 if(token != "supertux-addoninfo")
00142 {
00143 log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl;
00144 continue;
00145 }
00146 std::auto_ptr<Addon> addon(new Addon());
00147 addon->parse(*(iter.lisp()));
00148 addon->installed = false;
00149 addon->loaded = false;
00150
00151
00152 bool exists = false;
00153 for (std::vector<Addon*>::const_iterator i = addons.begin(); i != addons.end(); i++) {
00154 if (**i == *addon) {
00155 exists = true;
00156 break;
00157 }
00158 }
00159
00160 if (exists)
00161 {
00162
00163 }
00164 else if (addon->suggested_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos)
00165 {
00166
00167 log_warning << "Add-on \"" << addon->title << "\" contains unsafe file name. Skipping." << std::endl;
00168 }
00169 else
00170 {
00171 addons.push_back(addon.release());
00172 }
00173 }
00174 } catch(std::exception& e) {
00175 std::stringstream msg;
00176 msg << "Problem when reading Add-on list: " << e.what();
00177 throw std::runtime_error(msg.str());
00178 }
00179
00180 #endif
00181 }
00182
00183 void
00184 AddonManager::install(Addon* addon)
00185 {
00186 #ifdef HAVE_LIBCURL
00187
00188 if (addon->installed) throw std::runtime_error("Tried installing installed Add-on");
00189
00190
00191 if (addon->suggested_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
00192 throw std::runtime_error("Add-on has unsafe file name (\""+addon->suggested_filename+"\")");
00193 }
00194
00195 std::string fileName = addon->suggested_filename;
00196
00197
00198 if (PHYSFS_exists(fileName.c_str())) {
00199 fileName = addon->stored_md5 + "_" + addon->suggested_filename;
00200 if (PHYSFS_exists(fileName.c_str())) {
00201 throw std::runtime_error("Add-on of suggested filename already exists (\""+addon->suggested_filename+"\", \""+fileName+"\")");
00202 }
00203 }
00204
00205 char error_buffer[CURL_ERROR_SIZE+1];
00206
00207 char* url = (char*)malloc(addon->http_url.length() + 1);
00208 strncpy(url, addon->http_url.c_str(), addon->http_url.length() + 1);
00209
00210 PHYSFS_file* f = PHYSFS_openWrite(fileName.c_str());
00211
00212 log_debug << "Downloading \"" << url << "\"" << std::endl;
00213
00214 CURL *curl_handle;
00215 curl_handle = curl_easy_init();
00216 curl_easy_setopt(curl_handle, CURLOPT_URL, url);
00217 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
00218 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write);
00219 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f);
00220 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
00221 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
00222 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
00223 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
00224 CURLcode result = curl_easy_perform(curl_handle);
00225 curl_easy_cleanup(curl_handle);
00226
00227 PHYSFS_close(f);
00228
00229 free(url);
00230
00231 if (result != CURLE_OK) {
00232 PHYSFS_delete(fileName.c_str());
00233 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
00234 throw std::runtime_error("Downloading Add-on failed: " + why);
00235 }
00236
00237 addon->installed = true;
00238 addon->installed_physfs_filename = fileName;
00239 static const std::string writeDir = PHYSFS_getWriteDir();
00240 static const std::string dirSep = PHYSFS_getDirSeparator();
00241 addon->installed_absolute_filename = writeDir + dirSep + fileName;
00242 addon->loaded = false;
00243
00244 if (addon->get_md5() != addon->stored_md5) {
00245 addon->installed = false;
00246 PHYSFS_delete(fileName.c_str());
00247 std::string why = "MD5 checksums differ";
00248 throw std::runtime_error("Downloading Add-on failed: " + why);
00249 }
00250
00251 log_debug << "Finished downloading \"" << addon->installed_absolute_filename << "\". Enabling Add-on." << std::endl;
00252
00253 enable(addon);
00254
00255 #else
00256 (void) addon;
00257 #endif
00258
00259 }
00260
00261 void
00262 AddonManager::remove(Addon* addon)
00263 {
00264 if (!addon->installed) throw std::runtime_error("Tried removing non-installed Add-on");
00265
00266
00267
00268
00269 if (addon->installed_physfs_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
00270 throw std::runtime_error("Add-on has unsafe file name (\""+addon->installed_physfs_filename+"\")");
00271 }
00272
00273 unload(addon);
00274
00275 log_debug << "deleting file \"" << addon->installed_absolute_filename << "\"" << std::endl;
00276 PHYSFS_delete(addon->installed_absolute_filename.c_str());
00277 addon->installed = false;
00278
00279
00280 }
00281
00282 void
00283 AddonManager::disable(Addon* addon)
00284 {
00285 unload(addon);
00286
00287 std::string fileName = addon->installed_physfs_filename;
00288 if (std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName) == ignored_addon_filenames.end()) {
00289 ignored_addon_filenames.push_back(fileName);
00290 }
00291 }
00292
00293 void
00294 AddonManager::enable(Addon* addon)
00295 {
00296 load(addon);
00297
00298 std::string fileName = addon->installed_physfs_filename;
00299 std::vector<std::string>::iterator i = std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName);
00300 if (i != ignored_addon_filenames.end()) {
00301 ignored_addon_filenames.erase(i);
00302 }
00303 }
00304
00305 void
00306 AddonManager::unload(Addon* addon)
00307 {
00308 if (!addon->installed) throw std::runtime_error("Tried unloading non-installed Add-on");
00309 if (!addon->loaded) return;
00310
00311 log_debug << "Removing archive \"" << addon->installed_absolute_filename << "\" from search path" << std::endl;
00312 if (PHYSFS_removeFromSearchPath(addon->installed_absolute_filename.c_str()) == 0) {
00313 log_warning << "Could not remove " << addon->installed_absolute_filename << " from search path. Ignoring." << std::endl;
00314 return;
00315 }
00316
00317 addon->loaded = false;
00318 }
00319
00320 void
00321 AddonManager::load(Addon* addon)
00322 {
00323 if (!addon->installed) throw std::runtime_error("Tried loading non-installed Add-on");
00324 if (addon->loaded) return;
00325
00326 log_debug << "Adding archive \"" << addon->installed_absolute_filename << "\" to search path" << std::endl;
00327 if (PHYSFS_addToSearchPath(addon->installed_absolute_filename.c_str(), 0) == 0) {
00328 log_warning << "Could not add " << addon->installed_absolute_filename << " to search path. Ignoring." << std::endl;
00329 return;
00330 }
00331
00332 addon->loaded = true;
00333 }
00334
00335 void
00336 AddonManager::load_addons()
00337 {
00338
00339 for (std::vector<Addon*>::iterator i = addons.begin(); i != addons.end(); i++) {
00340 if ((*i)->installed && (*i)->loaded) unload(*i);
00341 delete *i;
00342 }
00343 addons.clear();
00344
00345
00346 char** rc = PHYSFS_enumerateFiles("/");
00347
00348 for(char** i = rc; *i != 0; ++i) {
00349
00350
00351 std::string fileName = *i;
00352
00353 const std::string archiveDir = PHYSFS_getRealDir(fileName.c_str());
00354 static const std::string dirSep = PHYSFS_getDirSeparator();
00355 std::string fullFilename = archiveDir + dirSep + fileName;
00356
00357
00358
00359
00360
00361
00362
00363
00364 static const std::string archiveExt = ".zip";
00365 if (fullFilename.compare(fullFilename.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
00366
00367
00368 struct stat stats;
00369 if (stat(fullFilename.c_str(), &stats) != 0) continue;
00370
00371
00372 if (!S_ISREG(stats.st_mode)) continue;
00373
00374 log_debug << "Found archive \"" << fullFilename << "\"" << std::endl;
00375
00376
00377 PHYSFS_addToSearchPath(fullFilename.c_str(), 0);
00378
00379
00380 std::string infoFileName = "";
00381 char** rc2 = PHYSFS_enumerateFiles("/");
00382 for(char** i = rc2; *i != 0; ++i) {
00383
00384
00385 std::string potentialInfoFileName = *i;
00386
00387
00388 static const std::string infoExt = ".nfo";
00389 if (potentialInfoFileName.length() <= infoExt.length())
00390 continue;
00391
00392 if (potentialInfoFileName.compare(potentialInfoFileName.length()-infoExt.length(), infoExt.length(), infoExt) != 0)
00393 continue;
00394
00395
00396 std::string infoFileDir = PHYSFS_getRealDir(potentialInfoFileName.c_str());
00397 if (infoFileDir != fullFilename) continue;
00398
00399
00400 infoFileName = potentialInfoFileName;
00401 break;
00402 }
00403 PHYSFS_freeList(rc2);
00404
00405
00406 if (infoFileName != "") {
00407 try {
00408 Addon* addon = new Addon();
00409 addon->parse(infoFileName);
00410 addon->installed = true;
00411 addon->installed_physfs_filename = fileName;
00412 addon->installed_absolute_filename = fullFilename;
00413 addon->loaded = true;
00414 addons.push_back(addon);
00415
00416
00417 if (std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName) != ignored_addon_filenames.end()) {
00418 unload(addon);
00419 }
00420
00421 } catch (const std::runtime_error& e) {
00422 log_warning << "Could not load add-on info for " << fullFilename << ", loading as unmanaged:" << e.what() << std::endl;
00423 }
00424 }
00425
00426 }
00427
00428 PHYSFS_freeList(rc);
00429 }
00430
00431 void
00432 AddonManager::read(const Reader& lisp)
00433 {
00434 lisp.get("disabled-addons", ignored_addon_filenames);
00435 }
00436
00437 void
00438 AddonManager::write(lisp::Writer& writer)
00439 {
00440 writer.write("disabled-addons", ignored_addon_filenames);
00441 }
00442
00443