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