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