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