1 /******************************************************************************
2  *  Warmux is a convivial mass murder game.
3  *  Copyright (C) 2001-2011 Warmux Team.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18  ******************************************************************************
19  * Download a file using libcurl
20  *****************************************************************************/
21 #include <cerrno>
22 #include <stdio.h>
23 #include <map>
24 #include <fstream>
25 #include <cstdlib>
26 #include <cstring>
27 #include <WARMUX_debug.h>
28 #include <WARMUX_download.h>
29 #include <WARMUX_error.h>
30 #include <WARMUX_i18n.h>
31 #include <WARMUX_file_tools.h>
32 
33 #ifdef HAVE_LIBCURL
34 # include <curl/curl.h>
35 
download_callback(void * buf,size_t size,size_t nmemb,void * fd)36 static size_t download_callback(void* buf, size_t size, size_t nmemb, void* fd)
37 {
38   return fwrite(buf, size, nmemb, (FILE*)fd);
39 }
40 
Downloader()41 Downloader::Downloader()
42 {
43 #ifdef WIN32
44   curl_global_init(CURL_GLOBAL_WIN32);
45 #else
46   curl_global_init(CURL_GLOBAL_NOTHING);
47 #endif
48 
49   curl = curl_easy_init();
50   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_callback);
51   curl_error_buf = new char[CURL_ERROR_SIZE];
52   curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_error_buf);
53   curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
54 }
55 
~Downloader()56 Downloader::~Downloader()
57 {
58   curl_easy_cleanup(curl);
59   curl_global_cleanup();
60   delete[] curl_error_buf;
61 }
62 
Get(const char * url,FILE * file)63 bool Downloader::Get(const char* url, FILE* file)
64 {
65   curl_easy_setopt(curl, CURLOPT_FILE, file);
66   curl_easy_setopt(curl, CURLOPT_URL, url);
67   CURLcode r = curl_easy_perform(curl);
68   fflush(file);
69 
70   if (r == CURLE_OK)
71     return true;
72 
73   error = std::string(curl_error_buf);
74   return false;
75 }
76 #elif defined(ANDROID)
77 # include <jni.h>
78 
79 #ifndef SDL_JAVA_PACKAGE_PATH
80 # error SDL_JAVA_PACKAGE_PATH undefined!
81 #endif
82 #define JAVA_EXPORT_NAME2(name,package) Java_##package##_##name
83 #define JAVA_EXPORT_NAME1(name,package) JAVA_EXPORT_NAME2(name,package)
84 #define JAVA_EXPORT_NAME(name) JAVA_EXPORT_NAME1(name,SDL_JAVA_PACKAGE_PATH)
85 
86 extern "C" {
87 
88 static JavaVM    *vm        = NULL;
89 
JNI_OnLoad(JavaVM * _vm,void * reserved)90 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *_vm, void *reserved)
91 {
92   vm = _vm;
93   return JNI_VERSION_1_2;
94 }
95 
JNI_OnUnload(JavaVM * _vm,void * reserved)96 JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *_vm, void *reserved)
97 {
98   vm = _vm;
99 }
100 
101 static JNIEnv    *env       = NULL;
102 static jmethodID  FetchURL  = NULL;
103 static jobject    dler      = NULL;
104 
105 void
JAVA_EXPORT_NAME(URLDownloader_nativeInitCallbacks)106 JAVA_EXPORT_NAME(URLDownloader_nativeInitCallbacks)(JNIEnv * libEnv, jobject thiz)
107 {
108   env  = libEnv;
109   dler = env->NewGlobalRef(thiz); //Never freed!
110   jclass dlerClass = env->GetObjectClass(dler);
111   FetchURL = env->GetMethodID(dlerClass, "FetchURL", "(Ljava/lang/String;)[B");
112 }
113 
114 };
115 
Downloader()116 Downloader::Downloader() { }
~Downloader()117 Downloader::~Downloader() { }
Get(const char * url,FILE * file)118 bool Downloader::Get(const char* url, FILE* file)
119 {
120   bool       ret    = false;
121   jboolean   isCopy = JNI_FALSE;
122   int        written;
123 
124   // Attach to avoid: "JNI ERROR: non-VM thread making JNI calls"
125   // Now make sure to properly detach even in case of error
126   vm->AttachCurrentThread(&env, NULL);
127 
128   jstring    jurl   = env->NewStringUTF(url);
129   jbyteArray buffer = (jbyteArray)env->CallObjectMethod(dler, FetchURL, jurl);
130   int        len    = env->GetArrayLength(buffer);
131   jbyte     *ptr;
132 
133   if (!len) {
134     error = Format(_("Read only %i bytes"), len);
135     goto out;
136   }
137 
138   ptr = env->GetByteArrayElements(buffer, &isCopy);
139   if (!ptr) {
140     error = _("No pointer");
141     goto out;
142   }
143 
144   written = fwrite(ptr, sizeof(jbyte), len, file);
145   if (isCopy == JNI_TRUE)
146     env->ReleaseByteArrayElements(buffer, ptr, 0);
147 
148 
149   if (written != len)
150     error = Format(_("Wrote %i/%i bytes"), written, len);
151   else
152     ret = true;
153 
154 out:
155   // Done with JNI calls, detach
156   vm->DetachCurrentThread();
157   return ret;
158 }
159 #else  // waiting for an alternate implementation
Downloader()160 Downloader::Downloader() { }
~Downloader()161 Downloader::~Downloader() { }
Get(const char *,FILE *)162 bool Downloader::Get(const char* /*url*/, FILE* /*file*/) { return false; }
163 #endif
164 
getline(std::string & line,FILE * file)165 static ssize_t getline(std::string& line, FILE* file)
166 {
167   line.clear();
168   char buffer[1024];
169 
170   int r = fscanf(file, "%1024s\n", buffer);
171   if (r == 1)
172     line = buffer;
173 
174   return line.size();
175 }
176 
GetLatestVersion(std::string & line)177 bool Downloader::GetLatestVersion(std::string& line)
178 {
179   static const char url[] = "http://www.warmux.org/last";
180   int fd;
181   const std::string last_file = CreateTmpFile("warmux_version", &fd);
182 
183   if (fd == -1) {
184     error = Format(_("Fail to create temporary file: %s"), strerror(errno));
185     fprintf(stderr, "%s\n", error.c_str());
186     return false;
187   }
188 
189   FILE* file = fdopen(fd, "r+");
190   if (!file) {
191     error = Format(_("Fail to open temporary file: %s"), strerror(errno));
192     fprintf(stderr, "%s\n", error.c_str());
193     return false;
194   }
195 
196   error.clear();
197   if (!Get(url, file)) {
198     if (error.empty())
199       error = Format(_("Couldn't fetch last version from %s"), url);
200     fprintf(stderr, "%s\n", error.c_str());
201     return false;
202   }
203 
204   // Parse the file
205   rewind(file);
206   getline(line, file);
207   fclose(file);
208 
209   // remove the file
210   remove(last_file.c_str());
211 
212   return true;
213 }
214 
GetServerList(std::map<std::string,int> & server_lst,const std::string & list_name)215 bool Downloader::GetServerList(std::map<std::string, int>& server_lst, const std::string& list_name)
216 {
217   MSG_DEBUG("downloader", "Retrieving server list: %s", list_name.c_str());
218 
219   // Download the list of server
220   const std::string list_url = "http://www.warmux.org/" + list_name;
221   int fd;
222   const std::string server_file = CreateTmpFile("warmux_servers", &fd);
223 
224   if (fd == -1) {
225     error = Format(_("Fail to create temporary file: %s"), strerror(errno));
226     fprintf(stderr, "%s\n", error.c_str());
227     return false;
228   }
229 
230   FILE* file = fdopen(fd, "r+");
231   if (!Get(list_url.c_str(), file))
232     return false;
233 
234   // Parse the file
235   std::string line;
236   rewind(file);
237 
238   // GNU getline isn't available on *BSD and Win32, so we use a new function, see getline above
239   while (getline(line, file) > 0) {
240     if (line.at(0) == '#'
241         || line.at(0) == '\n'
242         || line.at(0) == '\0')
243       continue;
244 
245     std::string::size_type port_pos = line.find(':', 0);
246     if (port_pos == std::string::npos)
247       continue;
248 
249     std::string hostname = line.substr(0, port_pos);
250     std::string portstr = line.substr(port_pos+1);
251     int port = atoi(portstr.c_str());
252 
253     server_lst[ hostname ] = port;
254   }
255 
256   fclose(file);
257   remove(server_file.c_str());
258 
259   MSG_DEBUG("downloader", "Server list retrieved. %u servers are running",
260             (uint)server_lst.size());
261 
262   return true;
263 }
264