X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=src%2Faddon_manager.cpp;h=b59dde0940d1f185a81d5dcd964f838f2c0602b1;hb=904852246d59d4988c3e85290dd4601713e42db5;hp=a2308ae7234811c32f31b50de05b59e30e209609;hpb=e4dec675baf3e23365862eb46ae765dcd31580d3;p=supertux.git diff --git a/src/addon_manager.cpp b/src/addon_manager.cpp index a2308ae72..b59dde094 100644 --- a/src/addon_manager.cpp +++ b/src/addon_manager.cpp @@ -19,7 +19,9 @@ // 02111-1307, USA. // +#include #include +#include #include #include #include @@ -27,6 +29,9 @@ #include "addon_manager.hpp" #include "config.h" #include "log.hpp" +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "lisp/list_iterator.hpp" #ifdef HAVE_LIBCURL #include @@ -106,17 +111,33 @@ AddonManager::get_installed_addons() const // make sure it's an actual file if (!S_ISREG(stats.st_mode)) continue; - // extract nice title + Addon addon; + + // extract nice title as fallback for when the Add-on has no addoninfo file static const char* dirSep = PHYSFS_getDirSeparator(); std::string::size_type n = fileName.rfind(dirSep) + 1; if (n == std::string::npos) n = 0; - std::string title = fileName.substr(n, fileName.length() - n - archiveExt.length()); + addon.title = fileName.substr(n, fileName.length() - n - archiveExt.length()); + std::string shortFileName = fileName.substr(n, fileName.length() - n); + addon.file = shortFileName; + + // read an accompaining .nfo file, if it exists + static const std::string infoExt = ".nfo"; + std::string infoFileName = fileName.substr(n, fileName.length() - n - archiveExt.length()) + infoExt; + if (PHYSFS_exists(infoFileName.c_str())) { + addon.parse(infoFileName); + if (addon.file != shortFileName) { + log_warning << "Add-on \"" << addon.title << "\", contained in file \"" << shortFileName << "\" is accompained by an addoninfo file that specifies \"" << addon.file << "\" as the Add-on's file name. Skipping." << std::endl; + } + } + + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; + continue; + } - Addon addon; - addon.title = title; - addon.fname = fileName; addon.isInstalled = true; - addons.push_back(addon); } @@ -130,66 +151,63 @@ AddonManager::get_available_addons() const #ifdef HAVE_LIBCURL - // FIXME: This URL is just for testing! - const char* baseUrl = "http://www.deltadevelopment.de/users/christoph/supertux/addons/"; - std::string html = ""; + char error_buffer[CURL_ERROR_SIZE+1]; + + const char* baseUrl = "http://supertux.berlios.de/addons/index.nfo"; + std::string addoninfos = ""; CURL *curl_handle; curl_handle = curl_easy_init(); curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl); curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &html); - curl_easy_perform(curl_handle); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos); + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); + CURLcode result = curl_easy_perform(curl_handle); curl_easy_cleanup(curl_handle); - //std::string html = "BlubbCoconut Fortress\nFoobarAnotherBaz"; - static const std::string startToken = "href=\""; - static const std::string endToken = "\""; - - // extract urls: for each startToken found... - std::string::size_type n = 0; - while ((n = html.find(startToken)) != std::string::npos) { - - // strip everything up to and including token - html.erase(0, n + startToken.length()); - - // find end token - std::string::size_type n2 = html.find(endToken); - if (n2 == std::string::npos) break; - - // extract url: it's the string inbetween - std::string url = html.substr(0, n2); - - // strip everything up to and including endToken - html.erase(0, n2 + endToken.length()); - - // make absolute url - url = std::string(baseUrl) + url; - - // make sure url looks like it points to an archive - static const std::string archiveExt = ".zip"; - if (url.compare(url.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue; - - // extract nice title - std::string::size_type n = url.rfind('/') + 1; - if (n == std::string::npos) n = 0; - std::string title = url.substr(n, url.length() - n - archiveExt.length()); - - // construct file name - std::string fname = url.substr(n); - - // make sure it does not contain weird characters - if (fname.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) continue; - - Addon addon; - addon.title = title; - addon.fname = fname; - addon.url = url; - addon.isInstalled = false; + if (result != CURLE_OK) { + std::string why = error_buffer[0] ? error_buffer : "unhandled error"; + throw std::runtime_error("Downloading Add-on list failed: " + why); + } - addons.push_back(addon); + try { + lisp::Parser parser; + std::stringstream addoninfos_stream(addoninfos); + const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons"); + + const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons"); + if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list"); + + lisp::ListIterator iter(addons_lisp); + while(iter.next()) { + const std::string& token = iter.item(); + if(token == "supertux-addoninfo") { + Addon addon; + addon.parse(*(iter.lisp())); + + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; + continue; + } + + addon.isInstalled = false; + addons.push_back(addon); + } else { + log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl; + } + } + } catch(std::exception& e) { + std::stringstream msg; + msg << "Problem when reading Add-on list: " << e.what(); + throw std::runtime_error(msg.str()); } + #endif return addons; @@ -199,13 +217,19 @@ AddonManager::get_available_addons() const void AddonManager::install(const Addon& addon) { + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")"); + } #ifdef HAVE_LIBCURL - char* url = (char*)malloc(addon.url.length() + 1); - strncpy(url, addon.url.c_str(), addon.url.length() + 1); + char error_buffer[CURL_ERROR_SIZE+1]; + + char* url = (char*)malloc(addon.http_url.length() + 1); + strncpy(url, addon.http_url.c_str(), addon.http_url.length() + 1); - PHYSFS_file* f = PHYSFS_openWrite(addon.fname.c_str()); + PHYSFS_file* f = PHYSFS_openWrite(addon.file.c_str()); log_debug << "Downloading \"" << url << "\"" << std::endl; @@ -215,16 +239,32 @@ AddonManager::install(const Addon& addon) curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL"); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f); - curl_easy_perform(curl_handle); + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1); + CURLcode result = curl_easy_perform(curl_handle); curl_easy_cleanup(curl_handle); PHYSFS_close(f); free(url); + if (result != CURLE_OK) { + PHYSFS_delete(addon.file.c_str()); + std::string why = error_buffer[0] ? error_buffer : "unhandled error"; + throw std::runtime_error("Downloading Add-on failed: " + why); + } + + // write an accompaining .nfo file + static const std::string archiveExt = ".zip"; + static const std::string infoExt = ".nfo"; + std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt; + addon.write(infoFileName); + static const std::string writeDir = PHYSFS_getWriteDir(); static const std::string dirSep = PHYSFS_getDirSeparator(); - std::string fullFilename = writeDir + dirSep + addon.fname; + std::string fullFilename = writeDir + dirSep + addon.file; log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl; PHYSFS_addToSearchPath(fullFilename.c_str(), 1); #else @@ -236,6 +276,22 @@ AddonManager::install(const Addon& addon) void AddonManager::remove(const Addon& addon) { - PHYSFS_removeFromSearchPath(addon.fname.c_str()); - PHYSFS_delete(addon.fname.c_str()); + // make sure the Add-on's file name does not contain weird characters + if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) { + throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")"); + } + + log_debug << "deleting file \"" << addon.file << "\"" << std::endl; + PHYSFS_removeFromSearchPath(addon.file.c_str()); + PHYSFS_delete(addon.file.c_str()); + + // remove an accompaining .nfo file + static const std::string archiveExt = ".zip"; + static const std::string infoExt = ".nfo"; + std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt; + if (PHYSFS_exists(infoFileName.c_str())) { + log_debug << "deleting file \"" << infoFileName << "\"" << std::endl; + PHYSFS_delete(infoFileName.c_str()); + } } +