| /* SPDX-License-Identifier: LGPL-2.1+ */ |
| /*** |
| This file is part of systemd. |
| |
| Copyright 2014 Lennart Poettering |
| |
| systemd is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2.1 of the License, or |
| (at your option) any later version. |
| |
| systemd is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with systemd; If not, see <http://www.gnu.org/licenses/>. |
| ***/ |
| |
| #include "alloc-util.h" |
| #include "curl-util.h" |
| #include "fd-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 ((revents & (EPOLLIN|EPOLLOUT)) == (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) < 0) { |
| log_debug("Failed to propagate IO event."); |
| return -EINVAL; |
| } |
| |
| 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) { |
| log_debug("Failed to propagate timeout."); |
| return -EINVAL; |
| } |
| |
| 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; |
| int r; |
| |
| g = new0(CurlGlue, 1); |
| if (!g) |
| return -ENOMEM; |
| |
| if (event) |
| g->event = sd_event_ref(event); |
| else { |
| r = sd_event_default(&g->event); |
| if (r < 0) |
| return r; |
| } |
| |
| g->curl = curl_multi_init(); |
| if (!g->curl) |
| return -ENOMEM; |
| |
| 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 = g; |
| g = NULL; |
| |
| return 0; |
| } |
| |
| int curl_glue_make(CURL **ret, const char *url, void *userdata) { |
| const char *useragent; |
| CURL *c; |
| int r; |
| |
| 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) { |
| r = -EIO; |
| goto fail; |
| } |
| |
| if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) { |
| r = -EIO; |
| goto fail; |
| } |
| |
| useragent = strjoina(program_invocation_short_name, "/" PACKAGE_VERSION); |
| if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) { |
| r = -EIO; |
| goto fail; |
| } |
| |
| if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) { |
| r = -EIO; |
| goto fail; |
| } |
| |
| *ret = c; |
| return 0; |
| |
| fail: |
| curl_easy_cleanup(c); |
| return r; |
| } |
| |
| 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 = contents; |
| size_t l; |
| char *s; |
| |
| l = strlen(field); |
| if (sz < l) |
| return 0; |
| |
| if (memcmp(p, field, l) != 0) |
| return 0; |
| |
| p += l; |
| sz -= l; |
| |
| 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) { |
| const char *e; |
| locale_t loc; |
| 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); |
| freelocale(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; |
| } |