chiark / gitweb /
d390cfb1f3e5f0978bfdfb075a300cae3d22dcca
[elogind.git] / src / import / curl-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include "curl-util.h"
23
24 static void curl_glue_check_finished(CurlGlue *g) {
25         CURLMsg *msg;
26         int k = 0;
27
28         assert(g);
29
30         msg = curl_multi_info_read(g->curl, &k);
31         if (!msg)
32                 return;
33
34         if (msg->msg != CURLMSG_DONE)
35                 return;
36
37         if (g->on_finished)
38                 g->on_finished(g, msg->easy_handle, msg->data.result);
39 }
40
41 static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
42         CurlGlue *g = userdata;
43         int action, k = 0, translated_fd;
44
45         assert(s);
46         assert(g);
47
48         translated_fd = PTR_TO_INT(hashmap_get(g->translate_fds, INT_TO_PTR(fd+1)));
49         assert(translated_fd > 0);
50         translated_fd--;
51
52         if ((revents & (EPOLLIN|EPOLLOUT)) == (EPOLLIN|EPOLLOUT))
53                 action = CURL_POLL_INOUT;
54         else if (revents & EPOLLIN)
55                 action = CURL_POLL_IN;
56         else if (revents & EPOLLOUT)
57                 action = CURL_POLL_OUT;
58         else
59                 action = 0;
60
61         if (curl_multi_socket_action(g->curl, translated_fd, action, &k) < 0) {
62                 log_debug("Failed to propagate IO event.");
63                 return -EINVAL;
64         }
65
66         curl_glue_check_finished(g);
67         return 0;
68 }
69
70 static int curl_glue_socket_callback(CURLM *curl, curl_socket_t s, int action, void *userdata, void *socketp) {
71         sd_event_source *io;
72         CurlGlue *g = userdata;
73         uint32_t events = 0;
74         int r;
75
76         assert(curl);
77         assert(g);
78
79         io = hashmap_get(g->ios, INT_TO_PTR(s+1));
80
81         if (action == CURL_POLL_REMOVE) {
82                 if (io) {
83                         int fd;
84
85                         fd = sd_event_source_get_io_fd(io);
86                         assert(fd >= 0);
87
88                         sd_event_source_set_enabled(io, SD_EVENT_OFF);
89                         sd_event_source_unref(io);
90
91                         hashmap_remove(g->ios, INT_TO_PTR(s+1));
92                         hashmap_remove(g->translate_fds, INT_TO_PTR(fd+1));
93
94                         safe_close(fd);
95                 }
96
97                 return 0;
98         }
99
100         r = hashmap_ensure_allocated(&g->ios, &trivial_hash_ops);
101         if (r < 0) {
102                 log_oom();
103                 return -1;
104         }
105
106         r = hashmap_ensure_allocated(&g->translate_fds, &trivial_hash_ops);
107         if (r < 0) {
108                 log_oom();
109                 return -1;
110         }
111
112         if (action == CURL_POLL_IN)
113                 events = EPOLLIN;
114         else if (action == CURL_POLL_OUT)
115                 events = EPOLLOUT;
116         else if (action == CURL_POLL_INOUT)
117                 events = EPOLLIN|EPOLLOUT;
118
119         if (io) {
120                 if (sd_event_source_set_io_events(io, events) < 0)
121                         return -1;
122
123                 if (sd_event_source_set_enabled(io, SD_EVENT_ON) < 0)
124                         return -1;
125         } else {
126                 _cleanup_close_ int fd = -1;
127
128                 /* When curl needs to remove an fd from us it closes
129                  * the fd first, and only then calls into us. This is
130                  * nasty, since we cannot pass the fd on to epoll()
131                  * anymore. Hence, duplicate the fds here, and keep a
132                  * copy for epoll which we control after use. */
133
134                 fd = fcntl(s, F_DUPFD_CLOEXEC, 3);
135                 if (fd < 0)
136                         return -1;
137
138                 if (sd_event_add_io(g->event, &io, fd, events, curl_glue_on_io, g) < 0)
139                         return -1;
140
141                 sd_event_source_set_description(io, "curl-io");
142
143                 r = hashmap_put(g->ios, INT_TO_PTR(s+1), io);
144                 if (r < 0) {
145                         log_oom();
146                         sd_event_source_unref(io);
147                         return -1;
148                 }
149
150                 r = hashmap_put(g->translate_fds, INT_TO_PTR(fd+1), INT_TO_PTR(s+1));
151                 if (r < 0) {
152                         log_oom();
153                         hashmap_remove(g->ios, INT_TO_PTR(s+1));
154                         sd_event_source_unref(io);
155                         return -1;
156                 }
157
158                 fd = -1;
159         }
160
161         return 0;
162 }
163
164 static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) {
165         CurlGlue *g = userdata;
166         int k = 0;
167
168         assert(s);
169         assert(g);
170
171         if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) {
172                 log_debug("Failed to propagate timeout.");
173                 return -EINVAL;
174         }
175
176         curl_glue_check_finished(g);
177         return 0;
178 }
179
180 static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) {
181         CurlGlue *g = userdata;
182         usec_t usec;
183
184         assert(curl);
185         assert(g);
186
187         if (timeout_ms < 0) {
188                 if (g->timer) {
189                         if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0)
190                                 return -1;
191                 }
192
193                 return 0;
194         }
195
196         usec = now(clock_boottime_or_monotonic()) + (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1;
197
198         if (g->timer) {
199                 if (sd_event_source_set_time(g->timer, usec) < 0)
200                         return -1;
201
202                 if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0)
203                         return -1;
204         } else {
205                 if (sd_event_add_time(g->event, &g->timer, clock_boottime_or_monotonic(), usec, 0, curl_glue_on_timer, g) < 0)
206                         return -1;
207
208                 sd_event_source_set_description(g->timer, "curl-timer");
209         }
210
211         return 0;
212 }
213
214 CurlGlue *curl_glue_unref(CurlGlue *g) {
215         sd_event_source *io;
216
217         if (!g)
218                 return NULL;
219
220         if (g->curl)
221                 curl_multi_cleanup(g->curl);
222
223         while ((io = hashmap_steal_first(g->ios))) {
224                 int fd;
225
226                 fd = sd_event_source_get_io_fd(io);
227                 assert(fd >= 0);
228
229                 hashmap_remove(g->translate_fds, INT_TO_PTR(fd+1));
230
231                 safe_close(fd);
232                 sd_event_source_unref(io);
233         }
234
235         hashmap_free(g->ios);
236
237         sd_event_source_unref(g->timer);
238         sd_event_unref(g->event);
239         free(g);
240
241         return NULL;
242 }
243
244 int curl_glue_new(CurlGlue **glue, sd_event *event) {
245         _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
246         int r;
247
248         g = new0(CurlGlue, 1);
249         if (!g)
250                 return -ENOMEM;
251
252         if (event)
253                 g->event = sd_event_ref(event);
254         else {
255                 r = sd_event_default(&g->event);
256                 if (r < 0)
257                         return r;
258         }
259
260         g->curl = curl_multi_init();
261         if (!g->curl)
262                 return -ENOMEM;
263
264         if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK)
265                 return -EINVAL;
266
267         if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK)
268                 return -EINVAL;
269
270         if (curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK)
271                 return -EINVAL;
272
273         if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK)
274                 return -EINVAL;
275
276         *glue = g;
277         g = NULL;
278
279         return 0;
280 }
281
282 int curl_glue_make(CURL **ret, const char *url, void *userdata) {
283         const char *useragent;
284         CURL *c;
285         int r;
286
287         assert(ret);
288         assert(url);
289
290         c = curl_easy_init();
291         if (!c)
292                 return -ENOMEM;
293
294         /* curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); */
295
296         if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) {
297                 r = -EIO;
298                 goto fail;
299         }
300
301         if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) {
302                 r = -EIO;
303                 goto fail;
304         }
305
306         useragent = strjoina(program_invocation_short_name, "/" PACKAGE_VERSION);
307         if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) {
308                 r = -EIO;
309                 goto fail;
310         }
311
312         if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) {
313                 r = -EIO;
314                 goto fail;
315         }
316
317         *ret = c;
318         return 0;
319
320 fail:
321         curl_easy_cleanup(c);
322         return r;
323 }
324
325 int curl_glue_add(CurlGlue *g, CURL *c) {
326         assert(g);
327         assert(c);
328
329         if (curl_multi_add_handle(g->curl, c) != CURLM_OK)
330                 return -EIO;
331
332         return 0;
333 }
334
335 void curl_glue_remove_and_free(CurlGlue *g, CURL *c) {
336         assert(g);
337
338         if (!c)
339                 return;
340
341         if (g->curl)
342                 curl_multi_remove_handle(g->curl, c);
343
344         curl_easy_cleanup(c);
345 }
346
347 struct curl_slist *curl_slist_new(const char *first, ...) {
348         struct curl_slist *l;
349         va_list ap;
350
351         if (!first)
352                 return NULL;
353
354         l = curl_slist_append(NULL, first);
355         if (!l)
356                 return NULL;
357
358         va_start(ap, first);
359
360         for (;;) {
361                 struct curl_slist *n;
362                 const char *i;
363
364                 i = va_arg(ap, const char*);
365                 if (!i)
366                         break;
367
368                 n = curl_slist_append(l, i);
369                 if (!n) {
370                         va_end(ap);
371                         curl_slist_free_all(l);
372                         return NULL;
373                 }
374
375                 l = n;
376         }
377
378         va_end(ap);
379         return l;
380 }
381
382 int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value) {
383         const char *p = contents;
384         size_t l;
385         char *s;
386
387         l = strlen(field);
388         if (sz < l)
389                 return 0;
390
391         if (memcmp(p, field, l) != 0)
392                 return 0;
393
394         p += l;
395         sz -= l;
396
397         if (memchr(p, 0, sz))
398                 return 0;
399
400         /* Skip over preceeding whitespace */
401         while (sz > 0 && strchr(WHITESPACE, p[0])) {
402                 p++;
403                 sz--;
404         }
405
406         /* Truncate trailing whitespace*/
407         while (sz > 0 && strchr(WHITESPACE, p[sz-1]))
408                 sz--;
409
410         s = strndup(p, sz);
411         if (!s)
412                 return -ENOMEM;
413
414         *value = s;
415         return 1;
416 }
417
418 int curl_parse_http_time(const char *t, usec_t *ret) {
419         const char *e;
420         locale_t loc;
421         struct tm tm;
422         time_t v;
423
424         assert(t);
425         assert(ret);
426
427         loc = newlocale(LC_TIME_MASK, "C", (locale_t) 0);
428         if (loc == (locale_t) 0)
429                 return -errno;
430
431         /* RFC822 */
432         e = strptime_l(t, "%a, %d %b %Y %H:%M:%S %Z", &tm, loc);
433         if (!e || *e != 0)
434                 /* RFC 850 */
435                 e = strptime_l(t, "%A, %d-%b-%y %H:%M:%S %Z", &tm, loc);
436         if (!e || *e != 0)
437                 /* ANSI C */
438                 e = strptime_l(t, "%a %b %d %H:%M:%S %Y", &tm, loc);
439         freelocale(loc);
440         if (!e || *e != 0)
441                 return -EINVAL;
442
443         v = timegm(&tm);
444         if (v == (time_t) -1)
445                 return -EINVAL;
446
447         *ret = (usec_t) v * USEC_PER_SEC;
448         return 0;
449 }