Removed files markes as questionable on http://supertux.lethargik.org/wiki/DataFiles
[supertux.git] / src / addon_manager.cpp
1 //  $Id$
2 //
3 //  SuperTux - Add-on Manager
4 //  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 //  02111-1307, USA.
20 //
21
22 #include <sstream>
23 #include <stdexcept>
24 #include <cstdlib>
25 #include <list>
26 #include <physfs.h>
27 #include <sys/stat.h>
28 #include <stdio.h>
29 #include "addon_manager.hpp"
30 #include "config.h"
31 #include "log.hpp"
32 #include "lisp/parser.hpp"
33 #include "lisp/lisp.hpp"
34 #include "lisp/list_iterator.hpp"
35
36 #ifdef HAVE_LIBCURL
37 #include <curl/curl.h>
38 #include <curl/types.h>
39 #include <curl/easy.h>
40 #endif
41
42 #ifdef HAVE_LIBCURL
43 namespace {
44
45   size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr)
46   {
47     std::string& s = *static_cast<std::string*>(string_ptr);
48     std::string buf(static_cast<char*>(ptr), size * nmemb);
49     s += buf;
50     log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
51     return size * nmemb;
52   }
53
54   size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p)
55   {
56     PHYSFS_file* f = static_cast<PHYSFS_file*>(f_p);
57     PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
58     log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
59     return size * written;
60   }
61
62 }
63 #endif
64
65 AddonManager&
66 AddonManager::get_instance()
67 {
68   static AddonManager instance;
69   return instance;
70 }
71
72 AddonManager::AddonManager()
73 {
74 #ifdef HAVE_LIBCURL
75   curl_global_init(CURL_GLOBAL_ALL);
76 #endif
77 }
78
79 AddonManager::~AddonManager()
80 {
81 #ifdef HAVE_LIBCURL
82   curl_global_cleanup();
83 #endif
84 }
85
86 std::vector<Addon>
87 AddonManager::get_installed_addons() const
88 {
89   std::vector<Addon> addons;
90
91   // iterate over complete search path (i.e. directories and archives)
92   char **i = PHYSFS_getSearchPath();
93   if (!i) throw std::runtime_error("Could not query physfs search path");
94   for (; *i != NULL; i++) {
95
96     // get filename of potential archive
97     std::string fileName = *i;
98
99     // make sure it's in the writeDir
100     static const std::string writeDir = PHYSFS_getWriteDir();
101     if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue;
102
103     // make sure it looks like an archive
104     static const std::string archiveExt = ".zip";
105     if (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
106
107     // make sure it exists
108     struct stat stats;
109     if (stat(fileName.c_str(), &stats) != 0) continue;
110
111     // make sure it's an actual file
112     if (!S_ISREG(stats.st_mode)) continue;
113
114     Addon addon;
115
116     // extract nice title as fallback for when the Add-on has no addoninfo file
117     static const char* dirSep = PHYSFS_getDirSeparator();
118     std::string::size_type n = fileName.rfind(dirSep) + 1;
119     if (n == std::string::npos) n = 0;
120     addon.title = fileName.substr(n, fileName.length() - n - archiveExt.length());
121     std::string shortFileName = fileName.substr(n, fileName.length() - n);
122     addon.file = shortFileName;
123    
124     // read an accompaining .nfo file, if it exists
125     static const std::string infoExt = ".nfo";
126     std::string infoFileName = fileName.substr(n, fileName.length() - n - archiveExt.length()) + infoExt;
127     if (PHYSFS_exists(infoFileName.c_str())) {
128       addon.parse(infoFileName);
129       if (addon.file != shortFileName) {
130         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;
131       }
132     }
133
134     // make sure the Add-on's file name does not contain weird characters
135     if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
136       log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl;
137       continue;
138     }
139
140     addon.isInstalled = true;
141     addons.push_back(addon);
142   }
143
144   return addons;
145 }
146
147 std::vector<Addon>
148 AddonManager::get_available_addons() const
149 {
150   std::vector<Addon> addons;
151
152 #ifdef HAVE_LIBCURL
153
154   char error_buffer[CURL_ERROR_SIZE+1];
155
156   const char* baseUrl = "http://supertux.berlios.de/addons/index.nfo";
157   std::string addoninfos = "";
158
159   CURL *curl_handle;
160   curl_handle = curl_easy_init();
161   curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl);
162   curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
163   curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append);
164   curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos);
165   curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
166   curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
167   curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
168   curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
169   curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
170   CURLcode result = curl_easy_perform(curl_handle);
171   curl_easy_cleanup(curl_handle);
172
173   if (result != CURLE_OK) {
174     std::string why = error_buffer[0] ? error_buffer : "unhandled error";
175     throw std::runtime_error("Downloading Add-on list failed: " + why);
176   }
177
178   try {
179     lisp::Parser parser;
180     std::stringstream addoninfos_stream(addoninfos);
181     const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons");
182
183     const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons");
184     if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list");
185
186     lisp::ListIterator iter(addons_lisp);
187     while(iter.next()) {
188       const std::string& token = iter.item();
189       if(token == "supertux-addoninfo") {
190         Addon addon;
191         addon.parse(*(iter.lisp()));
192
193         // make sure the Add-on's file name does not contain weird characters
194         if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
195           log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl;
196           continue;
197         }
198
199         addon.isInstalled = false;
200         addons.push_back(addon);
201       } else {
202         log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl;
203       }
204     }
205   } catch(std::exception& e) {
206     std::stringstream msg;
207     msg << "Problem when reading Add-on list: " << e.what();
208     throw std::runtime_error(msg.str());
209   }
210
211 #endif
212
213   return addons;
214 }
215
216
217 void
218 AddonManager::install(const Addon& addon)
219 {
220   // make sure the Add-on's file name does not contain weird characters
221   if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
222     throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")");
223   }
224
225 #ifdef HAVE_LIBCURL
226
227   char error_buffer[CURL_ERROR_SIZE+1];
228
229   char* url = (char*)malloc(addon.http_url.length() + 1);
230   strncpy(url, addon.http_url.c_str(), addon.http_url.length() + 1);
231
232   PHYSFS_file* f = PHYSFS_openWrite(addon.file.c_str());
233
234   log_debug << "Downloading \"" << url << "\"" << std::endl;
235
236   CURL *curl_handle;
237   curl_handle = curl_easy_init();
238   curl_easy_setopt(curl_handle, CURLOPT_URL, url);
239   curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
240   curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write);
241   curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f);
242   curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
243   curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
244   curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
245   curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
246   CURLcode result = curl_easy_perform(curl_handle);
247   curl_easy_cleanup(curl_handle);
248
249   PHYSFS_close(f);
250
251   free(url);
252
253   if (result != CURLE_OK) {
254     PHYSFS_delete(addon.file.c_str());
255     std::string why = error_buffer[0] ? error_buffer : "unhandled error";
256     throw std::runtime_error("Downloading Add-on failed: " + why);
257   }
258
259   // write an accompaining .nfo file
260   static const std::string archiveExt = ".zip";
261   static const std::string infoExt = ".nfo";
262   std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
263   addon.write(infoFileName);
264
265   static const std::string writeDir = PHYSFS_getWriteDir();
266   static const std::string dirSep = PHYSFS_getDirSeparator();
267   std::string fullFilename = writeDir + dirSep + addon.file;
268   log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl;
269   PHYSFS_addToSearchPath(fullFilename.c_str(), 1);
270 #else
271   (void) addon;
272 #endif
273
274 }
275
276 void
277 AddonManager::remove(const Addon& addon)
278 {
279   // make sure the Add-on's file name does not contain weird characters
280   if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
281     throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")");
282   }
283
284   log_debug << "deleting file \"" << addon.file << "\"" << std::endl;
285   PHYSFS_removeFromSearchPath(addon.file.c_str());
286   PHYSFS_delete(addon.file.c_str());
287
288   // remove an accompaining .nfo file
289   static const std::string archiveExt = ".zip";
290   static const std::string infoExt = ".nfo";
291   std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
292   if (PHYSFS_exists(infoFileName.c_str())) {
293     log_debug << "deleting file \"" << infoFileName << "\"" << std::endl;
294     PHYSFS_delete(infoFileName.c_str());
295   }
296 }
297