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