// 02111-1307, USA.
//
+#include <sstream>
#include <stdexcept>
+#include <cstdlib>
#include <list>
#include <physfs.h>
#include <sys/stat.h>
-#include <cstdio>
+#include <stdio.h>
#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 <curl/curl.h>
}
std::vector<Addon>
-AddonManager::get_addons() const
+AddonManager::get_installed_addons() const
{
std::vector<Addon> addons;
- // first step: search for installed addons
-
// iterate over complete search path (i.e. directories and archives)
char **i = PHYSFS_getSearchPath();
if (!i) throw std::runtime_error("Could not query physfs search path");
// 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);
}
+ return addons;
+}
+
+std::vector<Addon>
+AddonManager::get_available_addons() const
+{
+ std::vector<Addon> addons;
+
#ifdef HAVE_LIBCURL
- // second step: search for available addons
- // 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 = "Blubb<a href=\"http://www.deltadevelopment.de/users/christoph/supertux/addons/coconut_fortress.zip\">Coconut Fortress</a>\nFoobar<a href=\"http://www.deltadevelopment.de/users/christoph/supertux/addons/in_the_spring.zip\">Another</a>Baz";
- 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;
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;
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
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());
+ }
}
+