Contents of /trunk/src/net_io.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 296 - (show annotations)
Thu Sep 24 18:47:41 2009 UTC (14 years, 8 months ago) by harbaum
File MIME type: text/plain
File size: 11775 byte(s)
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 }