Parent Directory | Revision Log
Download aread size checks improved
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of OSM2Go. |
5 | * |
6 | * OSM2Go is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * OSM2Go 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 OSM2Go. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "appdata.h" |
21 | |
22 | #include <curl/curl.h> |
23 | #include <curl/types.h> /* new for v7 */ |
24 | #include <curl/easy.h> /* new for v7 */ |
25 | #include <unistd.h> |
26 | |
27 | static struct http_message_s { |
28 | int id; |
29 | char *msg; |
30 | } http_messages [] = { |
31 | { 0, "Curl internal failure" }, |
32 | { 200, "Ok" }, |
33 | { 400, "Bad Request (area too big?)" }, |
34 | { 401, "Unauthorized (wrong user/password?)" }, |
35 | { 403, "Forbidden" }, |
36 | { 404, "Not Found" }, |
37 | { 405, "Method Not Allowed" }, |
38 | { 410, "Gone" }, |
39 | { 412, "Precondition Failed" }, |
40 | { 417, "Expectation failed (expect rejected)" }, |
41 | { 500, "Internal Server Error" }, |
42 | { 503, "Service Unavailable" }, |
43 | { 0, NULL } |
44 | }; |
45 | |
46 | typedef struct { |
47 | char *ptr; |
48 | int len; |
49 | } curl_mem_t; |
50 | |
51 | typedef enum { NET_IO_DL_FILE, NET_IO_DL_MEM, NET_IO_DELETE } net_io_type_t; |
52 | |
53 | /* structure shared between worker and master thread */ |
54 | typedef struct { |
55 | net_io_type_t type; |
56 | gint refcount; /* reference counter for master and worker thread */ |
57 | |
58 | char *url, *user; |
59 | gboolean cancel; |
60 | float progress; |
61 | |
62 | /* curl/http related stuff: */ |
63 | CURLcode res; |
64 | long response; |
65 | char buffer[CURL_ERROR_SIZE]; |
66 | |
67 | /* request specific fields */ |
68 | union { |
69 | char *filename; /* used for NET_IO_DL_FILE */ |
70 | curl_mem_t mem; /* used for NET_IO_DL_MEM */ |
71 | }; |
72 | |
73 | /* system proxy settings if present */ |
74 | proxy_t *proxy; |
75 | |
76 | } net_io_request_t; |
77 | |
78 | static char *http_message(int id) { |
79 | struct http_message_s *msg = http_messages; |
80 | |
81 | while(msg->msg) { |
82 | if(msg->id == id) return _(msg->msg); |
83 | msg++; |
84 | } |
85 | |
86 | return NULL; |
87 | } |
88 | |
89 | static gint dialog_destroy_event(GtkWidget *widget, gpointer data) { |
90 | /* set cancel flag */ |
91 | *(gboolean*)data = TRUE; |
92 | return FALSE; |
93 | } |
94 | |
95 | static void on_cancel(GtkWidget *widget, gpointer data) { |
96 | /* set cancel flag */ |
97 | *(gboolean*)data = TRUE; |
98 | } |
99 | |
100 | /* create the dialog box shown while worker is running */ |
101 | static GtkWidget *busy_dialog(GtkWidget *parent, GtkWidget **pbar, |
102 | gboolean *cancel_ind) { |
103 | GtkWidget *dialog = gtk_dialog_new(); |
104 | |
105 | gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); |
106 | gtk_window_set_title(GTK_WINDOW(dialog), _("Downloading")); |
107 | gtk_window_set_default_size(GTK_WINDOW(dialog), 300, 10); |
108 | |
109 | gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); |
110 | gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent)); |
111 | |
112 | g_assert(pbar); |
113 | *pbar = gtk_progress_bar_new(); |
114 | gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(*pbar), 0.1); |
115 | |
116 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), *pbar); |
117 | |
118 | GtkWidget *button = gtk_button_new_with_label(_("Cancel")); |
119 | gtk_signal_connect(GTK_OBJECT(button), "clicked", |
120 | GTK_SIGNAL_FUNC(on_cancel), (gpointer)cancel_ind); |
121 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), button); |
122 | |
123 | gtk_signal_connect(GTK_OBJECT(dialog), "destroy", |
124 | G_CALLBACK(dialog_destroy_event), (gpointer)cancel_ind); |
125 | |
126 | gtk_widget_show_all(dialog); |
127 | |
128 | return dialog; |
129 | } |
130 | |
131 | static void request_free(net_io_request_t *request) { |
132 | /* decrease refcount and only free structure if no references are left */ |
133 | request->refcount--; |
134 | if(request->refcount) { |
135 | printf("still %d references, keeping request\n", request->refcount); |
136 | return; |
137 | } |
138 | |
139 | printf("no references left, freeing request\n"); |
140 | if(request->url) g_free(request->url); |
141 | if(request->user) g_free(request->user); |
142 | |
143 | /* filename is only a valid filename in NET_IO_DL_FILE mode */ |
144 | if(request->type == NET_IO_DL_FILE && request->filename) |
145 | g_free(request->filename); |
146 | |
147 | g_free(request); |
148 | } |
149 | |
150 | static int curl_progress_func(net_io_request_t *request, |
151 | double t, /* dltotal */ double d, /* dlnow */ |
152 | double ultotal, double ulnow) { |
153 | request->progress = t?d/t:0; |
154 | return 0; |
155 | } |
156 | |
157 | static size_t mem_write(void *ptr, size_t size, size_t nmemb, |
158 | void *stream) { |
159 | curl_mem_t *p = (curl_mem_t*)stream; |
160 | |
161 | p->ptr = g_realloc(p->ptr, p->len + size*nmemb + 1); |
162 | if(p->ptr) { |
163 | memcpy(p->ptr+p->len, ptr, size*nmemb); |
164 | p->len += size*nmemb; |
165 | p->ptr[p->len] = 0; |
166 | } |
167 | return nmemb; |
168 | } |
169 | |
170 | void net_io_set_proxy(CURL *curl, proxy_t *proxy) { |
171 | if(proxy) { |
172 | if(proxy->ignore_hosts) |
173 | printf("WARNING: Pproxy \"ignore_hosts\" unsupported!\n"); |
174 | |
175 | printf("net_io: using proxy %s:%d\n", proxy->host, proxy->port); |
176 | |
177 | curl_easy_setopt(curl, CURLOPT_PROXY, proxy->host); |
178 | curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy->port); |
179 | |
180 | if(proxy->use_authentication) { |
181 | printf("net_io: use auth for user %s\n", proxy->authentication_user); |
182 | |
183 | char *cred = g_strdup_printf("%s:%s", |
184 | proxy->authentication_user, |
185 | proxy->authentication_password); |
186 | |
187 | curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, cred); |
188 | g_free(cred); |
189 | } |
190 | } |
191 | } |
192 | |
193 | static void *worker_thread(void *ptr) { |
194 | net_io_request_t *request = (net_io_request_t*)ptr; |
195 | |
196 | printf("thread: running\n"); |
197 | |
198 | CURL *curl = curl_easy_init(); |
199 | if(curl) { |
200 | FILE *outfile = NULL; |
201 | gboolean ok = FALSE; |
202 | |
203 | /* prepare target (file, memory, ...) */ |
204 | switch(request->type) { |
205 | case NET_IO_DL_FILE: |
206 | outfile = fopen(request->filename, "w"); |
207 | ok = (outfile != NULL); |
208 | break; |
209 | |
210 | case NET_IO_DL_MEM: |
211 | request->mem.ptr = NULL; |
212 | request->mem.len = 0; |
213 | ok = TRUE; |
214 | break; |
215 | |
216 | default: |
217 | printf("thread: unsupported request\n"); |
218 | /* ugh?? */ |
219 | ok = TRUE; |
220 | break; |
221 | } |
222 | |
223 | if(ok) { |
224 | curl_easy_setopt(curl, CURLOPT_URL, request->url); |
225 | |
226 | switch(request->type) { |
227 | case NET_IO_DL_FILE: |
228 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); |
229 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); |
230 | break; |
231 | |
232 | case NET_IO_DL_MEM: |
233 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request->mem); |
234 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_write); |
235 | break; |
236 | |
237 | case NET_IO_DELETE: |
238 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); |
239 | break; |
240 | } |
241 | |
242 | net_io_set_proxy(curl, request->proxy); |
243 | |
244 | /* set user name and password for the authentication */ |
245 | if(request->user) |
246 | curl_easy_setopt(curl, CURLOPT_USERPWD, request->user); |
247 | |
248 | /* setup progress notification */ |
249 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); |
250 | curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress_func); |
251 | curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, request); |
252 | |
253 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, request->buffer); |
254 | |
255 | /* play nice and report some user agent */ |
256 | curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE "-libcurl/" VERSION); |
257 | |
258 | request->res = curl_easy_perform(curl); |
259 | printf("thread: curl perform returned with %d\n", request->res); |
260 | |
261 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &request->response); |
262 | |
263 | #if 0 |
264 | /* try to read "Error" */ |
265 | struct curl_slist *slist = NULL; |
266 | slist = curl_slist_append(slist, "Error:"); |
267 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); |
268 | #endif |
269 | |
270 | if(request->type == NET_IO_DL_FILE) |
271 | fclose(outfile); |
272 | } |
273 | |
274 | /* always cleanup */ |
275 | curl_easy_cleanup(curl); |
276 | } else |
277 | printf("thread: unable to init curl\n"); |
278 | |
279 | printf("thread: io done\n"); |
280 | request_free(request); |
281 | |
282 | printf("thread: terminating\n"); |
283 | return NULL; |
284 | } |
285 | |
286 | static gboolean net_io_do(GtkWidget *parent, net_io_request_t *request) { |
287 | /* the request structure is shared between master and worker thread. */ |
288 | /* typically the master thread will do some waiting until the worker */ |
289 | /* thread returns. But the master may very well stop waiting since e.g. */ |
290 | /* the user activated some cancel button. The client will learn this */ |
291 | /* from the fact that it's holding the only reference to the request */ |
292 | |
293 | GtkWidget *pbar = NULL; |
294 | GtkWidget *dialog = busy_dialog(parent, &pbar, &request->cancel); |
295 | |
296 | /* create worker thread */ |
297 | request->refcount = 2; // master and worker hold a reference |
298 | if(!g_thread_create(&worker_thread, request, FALSE, NULL) != 0) { |
299 | g_warning("failed to create the worker thread"); |
300 | |
301 | /* free request and return error */ |
302 | request->refcount--; /* decrease by one for dead worker thread */ |
303 | gtk_widget_destroy(dialog); |
304 | return FALSE; |
305 | } |
306 | |
307 | /* wait for worker thread */ |
308 | float progress = 0; |
309 | while(request->refcount > 1 && !request->cancel) { |
310 | while(gtk_events_pending()) |
311 | gtk_main_iteration(); |
312 | |
313 | /* worker has made progress changed the progress value */ |
314 | if(request->progress != progress) { |
315 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pbar), request->progress); |
316 | progress = request->progress; |
317 | } else |
318 | if(!progress) |
319 | gtk_progress_bar_pulse(GTK_PROGRESS_BAR(pbar)); |
320 | |
321 | usleep(100000); |
322 | } |
323 | |
324 | gtk_widget_destroy(dialog); |
325 | |
326 | /* user pressed cancel */ |
327 | if(request->refcount > 1) { |
328 | printf("operation cancelled, leave worker alone\n"); |
329 | return FALSE; |
330 | } |
331 | |
332 | printf("worker thread has ended\n"); |
333 | |
334 | /* --------- evaluate result --------- */ |
335 | |
336 | /* the http connection itself may have failed */ |
337 | if(request->res != 0) { |
338 | errorf(parent, _("Download failed with message:\n\n%s"), request->buffer); |
339 | return FALSE; |
340 | } |
341 | |
342 | /* a valid http connection may have returned an error */ |
343 | if(request->response != 200) { |
344 | errorf(parent, _("Download failed with code %ld:\n\n%s\n"), |
345 | request->response, http_message(request->response)); |
346 | return FALSE; |
347 | } |
348 | |
349 | return TRUE; |
350 | } |
351 | |
352 | gboolean net_io_download_file(GtkWidget *parent, settings_t *settings, |
353 | char *url, char *filename) { |
354 | net_io_request_t *request = g_new0(net_io_request_t, 1); |
355 | |
356 | printf("net_io: download %s to file %s\n", url, filename); |
357 | request->type = NET_IO_DL_FILE; |
358 | request->url = g_strdup(url); |
359 | request->filename = g_strdup(filename); |
360 | if(settings && settings->proxy) |
361 | request->proxy = settings->proxy; |
362 | |
363 | gboolean result = net_io_do(parent, request); |
364 | if(!result) { |
365 | |
366 | /* remove the file that may have been written by now. the kernel */ |
367 | /* should cope with the fact that the worker thread may still have */ |
368 | /* an open reference to this and might thus still write to this file. */ |
369 | /* letting the worker delete the file is worse since it may take the */ |
370 | /* worker some time to come to the point to delete this file. If the */ |
371 | /* user has restarted the download by then, the worker will erase that */ |
372 | /* newly written file */ |
373 | |
374 | printf("request failed, deleting %s\n", filename); |
375 | g_remove(filename); |
376 | } else |
377 | printf("request ok\n"); |
378 | |
379 | request_free(request); |
380 | return result; |
381 | } |
382 | |
383 | |
384 | gboolean net_io_download_mem(GtkWidget *parent, settings_t *settings, |
385 | char *url, char **mem) { |
386 | net_io_request_t *request = g_new0(net_io_request_t, 1); |
387 | |
388 | printf("net_io: download %s to memory\n", url); |
389 | request->type = NET_IO_DL_MEM; |
390 | request->url = g_strdup(url); |
391 | if(settings && settings->proxy) |
392 | request->proxy = settings->proxy; |
393 | |
394 | gboolean result = net_io_do(parent, request); |
395 | if(result) { |
396 | printf("ptr = %p, len = %d\n", request->mem.ptr, request->mem.len); |
397 | *mem = request->mem.ptr; |
398 | } |
399 | |
400 | request_free(request); |
401 | return result; |
402 | } |