#include "sentry_alloc.h" #include "sentry_core.h" #include "sentry_database.h" #include "sentry_envelope.h" #include "sentry_options.h" #include "sentry_ratelimiter.h" #include "sentry_string.h" #include "sentry_sync.h" #include "sentry_transport.h" #include "sentry_utils.h" #include #include #include #include typedef struct curl_transport_state_s { sentry_dsn_t *dsn; CURL *curl_handle; char *http_proxy; char *ca_certs; sentry_rate_limiter_t *ratelimiter; bool debug; } curl_bgworker_state_t; struct header_info { char *x_sentry_rate_limits; char *retry_after; }; static curl_bgworker_state_t * sentry__curl_bgworker_state_new(void) { curl_bgworker_state_t *state = SENTRY_MAKE(curl_bgworker_state_t); if (!state) { return NULL; } memset(state, 0, sizeof(curl_bgworker_state_t)); state->ratelimiter = sentry__rate_limiter_new(); return state; } static void sentry__curl_bgworker_state_free(void *_state) { curl_bgworker_state_t *state = _state; if (state->curl_handle) { curl_easy_cleanup(state->curl_handle); } sentry__dsn_decref(state->dsn); sentry__rate_limiter_free(state->ratelimiter); sentry_free(state->ca_certs); sentry_free(state->http_proxy); sentry_free(state); } static int sentry__curl_transport_start( const sentry_options_t *options, void *transport_state) { static bool curl_initialized = false; if (!curl_initialized) { CURLcode rv = curl_global_init(CURL_GLOBAL_ALL); if (rv != CURLE_OK) { SENTRY_WARNF("`curl_global_init` failed with code `%d`", (int)rv); return 1; } } sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; curl_bgworker_state_t *state = sentry__bgworker_get_state(bgworker); state->dsn = sentry__dsn_incref(options->dsn); state->http_proxy = sentry__string_clone(options->http_proxy); state->ca_certs = sentry__string_clone(options->ca_certs); state->curl_handle = curl_easy_init(); state->debug = options->debug; sentry__bgworker_setname(bgworker, options->transport_thread_name); if (!state->curl_handle) { // In this case we don’t start the worker at all, which means we can // still dump all unsent envelopes to disk on shutdown. SENTRY_WARN("`curl_easy_init` failed"); return 1; } return sentry__bgworker_start(bgworker); } static int sentry__curl_transport_flush(uint64_t timeout, void *transport_state) { sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; return sentry__bgworker_flush(bgworker, timeout); } static int sentry__curl_transport_shutdown(uint64_t timeout, void *transport_state) { sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; return sentry__bgworker_shutdown(bgworker, timeout); } static size_t swallow_data( char *UNUSED(ptr), size_t size, size_t nmemb, void *UNUSED(userdata)) { return size * nmemb; } static size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) { size_t bytes = size * nitems; struct header_info *info = userdata; char *header = sentry__string_clonen(buffer, bytes); if (!header) { return bytes; } char *sep = strchr(header, ':'); if (sep) { *sep = '\0'; sentry__string_ascii_lower(header); if (sentry__string_eq(header, "retry-after")) { info->retry_after = sentry__string_clone(sep + 1); } else if (sentry__string_eq(header, "x-sentry-rate-limits")) { info->x_sentry_rate_limits = sentry__string_clone(sep + 1); } } sentry_free(header); return bytes; } static void sentry__curl_send_task(void *_envelope, void *_state) { sentry_envelope_t *envelope = (sentry_envelope_t *)_envelope; curl_bgworker_state_t *state = (curl_bgworker_state_t *)_state; sentry_prepared_http_request_t *req = sentry__prepare_http_request( envelope, state->dsn, state->ratelimiter); if (!req) { return; } struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "expect:"); for (size_t i = 0; i < req->headers_len; i++) { char buf[512]; size_t written = (size_t)snprintf(buf, sizeof(buf), "%s:%s", req->headers[i].key, req->headers[i].value); if (written >= sizeof(buf)) { continue; } buf[written] = '\0'; headers = curl_slist_append(headers, buf); } CURL *curl = state->curl_handle; curl_easy_reset(curl); if (state->debug) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_WRITEDATA, stderr); // CURLOPT_WRITEFUNCTION will `fwrite` by default } else { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, swallow_data); } curl_easy_setopt(curl, CURLOPT_URL, req->url); curl_easy_setopt(curl, CURLOPT_POST, (long)1); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req->body); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)req->body_len); curl_easy_setopt(curl, CURLOPT_USERAGENT, SENTRY_SDK_USER_AGENT); char error_buf[CURL_ERROR_SIZE]; error_buf[0] = 0; curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf); struct header_info info; info.retry_after = NULL; info.x_sentry_rate_limits = NULL; curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&info); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); if (state->http_proxy) { curl_easy_setopt(curl, CURLOPT_PROXY, state->http_proxy); } if (state->ca_certs) { curl_easy_setopt(curl, CURLOPT_CAINFO, state->ca_certs); } CURLcode rv = curl_easy_perform(curl); if (rv == CURLE_OK) { long response_code; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); if (info.x_sentry_rate_limits) { sentry__rate_limiter_update_from_header( state->ratelimiter, info.x_sentry_rate_limits); } else if (info.retry_after) { sentry__rate_limiter_update_from_http_retry_after( state->ratelimiter, info.retry_after); } else if (response_code == 429) { sentry__rate_limiter_update_from_429(state->ratelimiter); } } else { size_t len = strlen(error_buf); if (len) { if (error_buf[len - 1] == '\n') { error_buf[len - 1] = 0; } SENTRY_WARNF("`curl_easy_perform` failed with code `%d`: %s", (int)rv, error_buf); } else { SENTRY_WARNF("`curl_easy_perform` failed with code `%d`: %s", (int)rv, curl_easy_strerror(rv)); } } curl_slist_free_all(headers); sentry_free(info.retry_after); sentry_free(info.x_sentry_rate_limits); sentry__prepared_http_request_free(req); } static void sentry__curl_transport_send_envelope( sentry_envelope_t *envelope, void *transport_state) { sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; sentry__bgworker_submit(bgworker, sentry__curl_send_task, (void (*)(void *))sentry_envelope_free, envelope); } static bool sentry__curl_dump_task(void *envelope, void *run) { sentry__run_write_envelope( (sentry_run_t *)run, (sentry_envelope_t *)envelope); return true; } size_t sentry__curl_dump_queue(sentry_run_t *run, void *transport_state) { sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; return sentry__bgworker_foreach_matching( bgworker, sentry__curl_send_task, sentry__curl_dump_task, run); } sentry_transport_t * sentry__transport_new_default(void) { SENTRY_DEBUG("initializing curl transport"); curl_bgworker_state_t *state = sentry__curl_bgworker_state_new(); if (!state) { return NULL; } sentry_bgworker_t *bgworker = sentry__bgworker_new(state, sentry__curl_bgworker_state_free); if (!bgworker) { return NULL; } sentry_transport_t *transport = sentry_transport_new(sentry__curl_transport_send_envelope); if (!transport) { sentry__bgworker_decref(bgworker); return NULL; } sentry_transport_set_state(transport, bgworker); sentry_transport_set_free_func( transport, (void (*)(void *))sentry__bgworker_decref); sentry_transport_set_startup_func(transport, sentry__curl_transport_start); sentry_transport_set_flush_func(transport, sentry__curl_transport_flush); sentry_transport_set_shutdown_func( transport, sentry__curl_transport_shutdown); sentry__transport_set_dump_func(transport, sentry__curl_dump_queue); return transport; }