diff options
Diffstat (limited to 'src/import/curl-util.c')
-rw-r--r-- | src/import/curl-util.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/src/import/curl-util.c b/src/import/curl-util.c new file mode 100644 index 0000000..7db03b2 --- /dev/null +++ b/src/import/curl-util.c @@ -0,0 +1,416 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "alloc-util.h" +#include "build.h" +#include "curl-util.h" +#include "fd-util.h" +#include "locale-util.h" +#include "string-util.h" + +static void curl_glue_check_finished(CurlGlue *g) { + CURLMsg *msg; + int k = 0; + + assert(g); + + msg = curl_multi_info_read(g->curl, &k); + if (!msg) + return; + + if (msg->msg != CURLMSG_DONE) + return; + + if (g->on_finished) + g->on_finished(g, msg->easy_handle, msg->data.result); +} + +static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + CurlGlue *g = userdata; + int action, k = 0, translated_fd; + + assert(s); + assert(g); + + translated_fd = PTR_TO_FD(hashmap_get(g->translate_fds, FD_TO_PTR(fd))); + + if (FLAGS_SET(revents, EPOLLIN | EPOLLOUT)) + action = CURL_POLL_INOUT; + else if (revents & EPOLLIN) + action = CURL_POLL_IN; + else if (revents & EPOLLOUT) + action = CURL_POLL_OUT; + else + action = 0; + + if (curl_multi_socket_action(g->curl, translated_fd, action, &k) != CURLM_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to propagate IO event."); + + curl_glue_check_finished(g); + return 0; +} + +static int curl_glue_socket_callback(CURLM *curl, curl_socket_t s, int action, void *userdata, void *socketp) { + sd_event_source *io; + CurlGlue *g = userdata; + uint32_t events = 0; + int r; + + assert(curl); + assert(g); + + io = hashmap_get(g->ios, FD_TO_PTR(s)); + + if (action == CURL_POLL_REMOVE) { + if (io) { + int fd; + + fd = sd_event_source_get_io_fd(io); + assert(fd >= 0); + + sd_event_source_set_enabled(io, SD_EVENT_OFF); + sd_event_source_unref(io); + + hashmap_remove(g->ios, FD_TO_PTR(s)); + hashmap_remove(g->translate_fds, FD_TO_PTR(fd)); + + safe_close(fd); + } + + return 0; + } + + r = hashmap_ensure_allocated(&g->ios, &trivial_hash_ops); + if (r < 0) { + log_oom(); + return -1; + } + + r = hashmap_ensure_allocated(&g->translate_fds, &trivial_hash_ops); + if (r < 0) { + log_oom(); + return -1; + } + + if (action == CURL_POLL_IN) + events = EPOLLIN; + else if (action == CURL_POLL_OUT) + events = EPOLLOUT; + else if (action == CURL_POLL_INOUT) + events = EPOLLIN|EPOLLOUT; + + if (io) { + if (sd_event_source_set_io_events(io, events) < 0) + return -1; + + if (sd_event_source_set_enabled(io, SD_EVENT_ON) < 0) + return -1; + } else { + _cleanup_close_ int fd = -1; + + /* When curl needs to remove an fd from us it closes + * the fd first, and only then calls into us. This is + * nasty, since we cannot pass the fd on to epoll() + * anymore. Hence, duplicate the fds here, and keep a + * copy for epoll which we control after use. */ + + fd = fcntl(s, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return -1; + + if (sd_event_add_io(g->event, &io, fd, events, curl_glue_on_io, g) < 0) + return -1; + + (void) sd_event_source_set_description(io, "curl-io"); + + r = hashmap_put(g->ios, FD_TO_PTR(s), io); + if (r < 0) { + log_oom(); + sd_event_source_unref(io); + return -1; + } + + r = hashmap_put(g->translate_fds, FD_TO_PTR(fd), FD_TO_PTR(s)); + if (r < 0) { + log_oom(); + hashmap_remove(g->ios, FD_TO_PTR(s)); + sd_event_source_unref(io); + return -1; + } + + fd = -1; + } + + return 0; +} + +static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) { + CurlGlue *g = userdata; + int k = 0; + + assert(s); + assert(g); + + if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to propagate timeout."); + + curl_glue_check_finished(g); + return 0; +} + +static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) { + CurlGlue *g = userdata; + usec_t usec; + + assert(curl); + assert(g); + + if (timeout_ms < 0) { + if (g->timer) { + if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) + return -1; + } + + return 0; + } + + usec = now(clock_boottime_or_monotonic()) + (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1; + + if (g->timer) { + if (sd_event_source_set_time(g->timer, usec) < 0) + return -1; + + if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0) + return -1; + } else { + if (sd_event_add_time(g->event, &g->timer, clock_boottime_or_monotonic(), usec, 0, curl_glue_on_timer, g) < 0) + return -1; + + (void) sd_event_source_set_description(g->timer, "curl-timer"); + } + + return 0; +} + +CurlGlue *curl_glue_unref(CurlGlue *g) { + sd_event_source *io; + + if (!g) + return NULL; + + if (g->curl) + curl_multi_cleanup(g->curl); + + while ((io = hashmap_steal_first(g->ios))) { + int fd; + + fd = sd_event_source_get_io_fd(io); + assert(fd >= 0); + + hashmap_remove(g->translate_fds, FD_TO_PTR(fd)); + + safe_close(fd); + sd_event_source_unref(io); + } + + hashmap_free(g->ios); + + sd_event_source_unref(g->timer); + sd_event_unref(g->event); + return mfree(g); +} + +int curl_glue_new(CurlGlue **glue, sd_event *event) { + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + _cleanup_(curl_multi_cleanupp) CURL *c = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + int r; + + if (event) + e = sd_event_ref(event); + else { + r = sd_event_default(&e); + if (r < 0) + return r; + } + + c = curl_multi_init(); + if (!c) + return -ENOMEM; + + g = new(CurlGlue, 1); + if (!g) + return -ENOMEM; + + *g = (CurlGlue) { + .event = TAKE_PTR(e), + .curl = TAKE_PTR(c), + }; + + if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) + return -EINVAL; + + if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) + return -EINVAL; + + if (curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) + return -EINVAL; + + if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) + return -EINVAL; + + *glue = TAKE_PTR(g); + + return 0; +} + +int curl_glue_make(CURL **ret, const char *url, void *userdata) { + _cleanup_(curl_easy_cleanupp) CURL *c = NULL; + const char *useragent; + + assert(ret); + assert(url); + + c = curl_easy_init(); + if (!c) + return -ENOMEM; + + /* curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); */ + + if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) + return -EIO; + + useragent = strjoina(program_invocation_short_name, "/" GIT_VERSION); + if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) + return -EIO; + + if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) + return -EIO; + + *ret = TAKE_PTR(c); + return 0; +} + +int curl_glue_add(CurlGlue *g, CURL *c) { + assert(g); + assert(c); + + if (curl_multi_add_handle(g->curl, c) != CURLM_OK) + return -EIO; + + return 0; +} + +void curl_glue_remove_and_free(CurlGlue *g, CURL *c) { + assert(g); + + if (!c) + return; + + if (g->curl) + curl_multi_remove_handle(g->curl, c); + + curl_easy_cleanup(c); +} + +struct curl_slist *curl_slist_new(const char *first, ...) { + struct curl_slist *l; + va_list ap; + + if (!first) + return NULL; + + l = curl_slist_append(NULL, first); + if (!l) + return NULL; + + va_start(ap, first); + + for (;;) { + struct curl_slist *n; + const char *i; + + i = va_arg(ap, const char*); + if (!i) + break; + + n = curl_slist_append(l, i); + if (!n) { + va_end(ap); + curl_slist_free_all(l); + return NULL; + } + + l = n; + } + + va_end(ap); + return l; +} + +int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value) { + const char *p; + char *s; + + p = memory_startswith_no_case(contents, sz, field); + if (!p) + return 0; + + sz -= p - (const char*) contents; + + if (memchr(p, 0, sz)) + return 0; + + /* Skip over preceeding whitespace */ + while (sz > 0 && strchr(WHITESPACE, p[0])) { + p++; + sz--; + } + + /* Truncate trailing whitespace */ + while (sz > 0 && strchr(WHITESPACE, p[sz-1])) + sz--; + + s = strndup(p, sz); + if (!s) + return -ENOMEM; + + *value = s; + return 1; +} + +int curl_parse_http_time(const char *t, usec_t *ret) { + _cleanup_(freelocalep) locale_t loc = (locale_t) 0; + const char *e; + struct tm tm; + time_t v; + + assert(t); + assert(ret); + + loc = newlocale(LC_TIME_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) + return -errno; + + /* RFC822 */ + e = strptime_l(t, "%a, %d %b %Y %H:%M:%S %Z", &tm, loc); + if (!e || *e != 0) + /* RFC 850 */ + e = strptime_l(t, "%A, %d-%b-%y %H:%M:%S %Z", &tm, loc); + if (!e || *e != 0) + /* ANSI C */ + e = strptime_l(t, "%a %b %d %H:%M:%S %Y", &tm, loc); + if (!e || *e != 0) + return -EINVAL; + + v = timegm(&tm); + if (v == (time_t) -1) + return -EINVAL; + + *ret = (usec_t) v * USEC_PER_SEC; + return 0; +} |