#include "sentry_envelope.h" #include "sentry_alloc.h" #include "sentry_core.h" #include "sentry_json.h" #include "sentry_options.h" #include "sentry_path.h" #include "sentry_ratelimiter.h" #include "sentry_string.h" #include "sentry_transport.h" #include "sentry_value.h" #include struct sentry_envelope_item_s { sentry_value_t headers; sentry_value_t event; char *payload; size_t payload_len; }; struct sentry_envelope_s { bool is_raw; union { struct { sentry_value_t headers; sentry_envelope_item_t items[SENTRY_MAX_ENVELOPE_ITEMS]; size_t item_count; } items; struct { char *payload; size_t payload_len; } raw; } contents; }; static sentry_envelope_item_t * envelope_add_item(sentry_envelope_t *envelope) { if (envelope->is_raw) { return NULL; } if (envelope->contents.items.item_count >= SENTRY_MAX_ENVELOPE_ITEMS) { return NULL; } // TODO: Envelopes may have at most one event item or one transaction item, // and not one of both. Some checking should be done here or in // `sentry__envelope_add_[transaction|event]` to ensure this can't happen. sentry_envelope_item_t *rv = &envelope->contents.items .items[envelope->contents.items.item_count++]; rv->headers = sentry_value_new_object(); rv->event = sentry_value_new_null(); rv->payload = NULL; rv->payload_len = 0; return rv; } static void envelope_item_cleanup(sentry_envelope_item_t *item) { sentry_value_decref(item->headers); sentry_value_decref(item->event); sentry_free(item->payload); } void sentry__envelope_item_set_header( sentry_envelope_item_t *item, const char *key, sentry_value_t value) { sentry_value_set_by_key(item->headers, key, value); } static int envelope_item_get_ratelimiter_category(const sentry_envelope_item_t *item) { const char *ty = sentry_value_as_string( sentry_value_get_by_key(item->headers, "type")); if (sentry__string_eq(ty, "session")) { return SENTRY_RL_CATEGORY_SESSION; } else if (sentry__string_eq(ty, "transaction")) { return SENTRY_RL_CATEGORY_TRANSACTION; } // NOTE: the `type` here can be `event` or `attachment`. // Ideally, attachments should have their own RL_CATEGORY. return SENTRY_RL_CATEGORY_ERROR; } static sentry_envelope_item_t * envelope_add_from_owned_buffer( sentry_envelope_t *envelope, char *buf, size_t buf_len, const char *type) { if (!buf) { return NULL; } sentry_envelope_item_t *item = envelope_add_item(envelope); if (!item) { sentry_free(buf); return NULL; } item->payload = buf; item->payload_len = buf_len; sentry_value_t length = sentry_value_new_int32((int32_t)buf_len); sentry__envelope_item_set_header( item, "type", sentry_value_new_string(type)); sentry__envelope_item_set_header(item, "length", length); return item; } void sentry_envelope_free(sentry_envelope_t *envelope) { if (!envelope) { return; } if (envelope->is_raw) { sentry_free(envelope->contents.raw.payload); sentry_free(envelope); return; } sentry_value_decref(envelope->contents.items.headers); for (size_t i = 0; i < envelope->contents.items.item_count; i++) { envelope_item_cleanup(&envelope->contents.items.items[i]); } sentry_free(envelope); } static void sentry__envelope_set_header( sentry_envelope_t *envelope, const char *key, sentry_value_t value) { if (envelope->is_raw) { return; } sentry_value_set_by_key(envelope->contents.items.headers, key, value); } sentry_envelope_t * sentry__envelope_new(void) { sentry_envelope_t *rv = SENTRY_MAKE(sentry_envelope_t); if (!rv) { return NULL; } rv->is_raw = false; rv->contents.items.item_count = 0; rv->contents.items.headers = sentry_value_new_object(); SENTRY_WITH_OPTIONS (options) { if (options->dsn && options->dsn->is_valid) { sentry__envelope_set_header(rv, "dsn", sentry_value_new_string(sentry_options_get_dsn(options))); } } return rv; } sentry_envelope_t * sentry__envelope_from_path(const sentry_path_t *path) { size_t buf_len; char *buf = sentry__path_read_to_buffer(path, &buf_len); if (!buf) { SENTRY_WARNF("failed to read raw envelope from \"%" SENTRY_PATH_PRI "\"", path->path); return NULL; } sentry_envelope_t *envelope = SENTRY_MAKE(sentry_envelope_t); if (!envelope) { sentry_free(buf); return NULL; } envelope->is_raw = true; envelope->contents.raw.payload = buf; envelope->contents.raw.payload_len = buf_len; return envelope; } sentry_uuid_t sentry__envelope_get_event_id(const sentry_envelope_t *envelope) { if (envelope->is_raw) { return sentry_uuid_nil(); } return sentry_uuid_from_string(sentry_value_as_string( sentry_value_get_by_key(envelope->contents.items.headers, "event_id"))); } sentry_value_t sentry_envelope_get_event(const sentry_envelope_t *envelope) { if (envelope->is_raw) { return sentry_value_new_null(); } for (size_t i = 0; i < envelope->contents.items.item_count; i++) { if (!sentry_value_is_null(envelope->contents.items.items[i].event) && !sentry__event_is_transaction( envelope->contents.items.items[i].event)) { return envelope->contents.items.items[i].event; } } return sentry_value_new_null(); } sentry_value_t sentry_envelope_get_transaction(const sentry_envelope_t *envelope) { if (envelope->is_raw) { return sentry_value_new_null(); } for (size_t i = 0; i < envelope->contents.items.item_count; i++) { if (!sentry_value_is_null(envelope->contents.items.items[i].event) && sentry__event_is_transaction( envelope->contents.items.items[i].event)) { return envelope->contents.items.items[i].event; } } return sentry_value_new_null(); } sentry_envelope_item_t * sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event) { sentry_envelope_item_t *item = envelope_add_item(envelope); if (!item) { return NULL; } sentry_jsonwriter_t *jw = sentry__jsonwriter_new(NULL); if (!jw) { return NULL; } sentry_value_t event_id = sentry__ensure_event_id(event, NULL); item->event = event; sentry__jsonwriter_write_value(jw, event); item->payload = sentry__jsonwriter_into_string(jw, &item->payload_len); sentry__envelope_item_set_header( item, "type", sentry_value_new_string("event")); sentry_value_t length = sentry_value_new_int32((int32_t)item->payload_len); sentry__envelope_item_set_header(item, "length", length); sentry_value_incref(event_id); sentry__envelope_set_header(envelope, "event_id", event_id); return item; } sentry_envelope_item_t * sentry__envelope_add_transaction( sentry_envelope_t *envelope, sentry_value_t transaction) { sentry_envelope_item_t *item = envelope_add_item(envelope); if (!item) { return NULL; } sentry_jsonwriter_t *jw = sentry__jsonwriter_new(NULL); if (!jw) { return NULL; } sentry_value_t event_id = sentry__ensure_event_id(transaction, NULL); item->event = transaction; sentry__jsonwriter_write_value(jw, transaction); item->payload = sentry__jsonwriter_into_string(jw, &item->payload_len); sentry__envelope_item_set_header( item, "type", sentry_value_new_string("transaction")); sentry_value_t length = sentry_value_new_int32((int32_t)item->payload_len); sentry__envelope_item_set_header(item, "length", length); sentry_value_incref(event_id); sentry__envelope_set_header(envelope, "event_id", event_id); #ifdef SENTRY_UNITTEST sentry_value_t now = sentry_value_new_string("2021-12-16T05:53:59.343Z"); #else sentry_value_t now = sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time())); #endif sentry__envelope_set_header(envelope, "sent_at", now); return item; } sentry_envelope_item_t * sentry__envelope_add_session( sentry_envelope_t *envelope, const sentry_session_t *session) { if (!envelope || !session) { return NULL; } sentry_jsonwriter_t *jw = sentry__jsonwriter_new(NULL); if (!jw) { return NULL; } sentry__session_to_json(session, jw); size_t payload_len = 0; char *payload = sentry__jsonwriter_into_string(jw, &payload_len); // NOTE: function will check for `payload` internally and free it on error return envelope_add_from_owned_buffer( envelope, payload, payload_len, "session"); } sentry_envelope_item_t * sentry__envelope_add_from_buffer(sentry_envelope_t *envelope, const char *buf, size_t buf_len, const char *type) { // NOTE: function will check for the clone of `buf` internally and free it // on error return envelope_add_from_owned_buffer( envelope, sentry__string_clonen(buf, buf_len), buf_len, type); } sentry_envelope_item_t * sentry__envelope_add_from_path( sentry_envelope_t *envelope, const sentry_path_t *path, const char *type) { if (!envelope) { return NULL; } size_t buf_len; char *buf = sentry__path_read_to_buffer(path, &buf_len); if (!buf) { SENTRY_WARNF("failed to read envelope item from \"%" SENTRY_PATH_PRI "\"", path->path); return NULL; } // NOTE: function will free `buf` on error return envelope_add_from_owned_buffer(envelope, buf, buf_len, type); } static void sentry__envelope_serialize_headers_into_stringbuilder( const sentry_envelope_t *envelope, sentry_stringbuilder_t *sb) { sentry_jsonwriter_t *jw = sentry__jsonwriter_new(sb); if (jw) { sentry__jsonwriter_write_value(jw, envelope->contents.items.headers); sentry__jsonwriter_free(jw); } } static void sentry__envelope_serialize_item_into_stringbuilder( const sentry_envelope_item_t *item, sentry_stringbuilder_t *sb) { sentry_jsonwriter_t *jw = sentry__jsonwriter_new(sb); if (!jw) { return; } sentry__stringbuilder_append_char(sb, '\n'); sentry__jsonwriter_write_value(jw, item->headers); sentry__jsonwriter_free(jw); sentry__stringbuilder_append_char(sb, '\n'); sentry__stringbuilder_append_buf(sb, item->payload, item->payload_len); } void sentry__envelope_serialize_into_stringbuilder( const sentry_envelope_t *envelope, sentry_stringbuilder_t *sb) { if (envelope->is_raw) { sentry__stringbuilder_append_buf(sb, envelope->contents.raw.payload, envelope->contents.raw.payload_len); return; } SENTRY_TRACE("serializing envelope into buffer"); sentry__envelope_serialize_headers_into_stringbuilder(envelope, sb); for (size_t i = 0; i < envelope->contents.items.item_count; i++) { const sentry_envelope_item_t *item = &envelope->contents.items.items[i]; sentry__envelope_serialize_item_into_stringbuilder(item, sb); } } char * sentry_envelope_serialize_ratelimited(const sentry_envelope_t *envelope, const sentry_rate_limiter_t *rl, size_t *size_out, bool *owned_out) { if (envelope->is_raw) { *size_out = envelope->contents.raw.payload_len; *owned_out = false; return envelope->contents.raw.payload; } *owned_out = true; sentry_stringbuilder_t sb; sentry__stringbuilder_init(&sb); sentry__envelope_serialize_headers_into_stringbuilder(envelope, &sb); size_t serialized_items = 0; for (size_t i = 0; i < envelope->contents.items.item_count; i++) { const sentry_envelope_item_t *item = &envelope->contents.items.items[i]; if (rl) { int category = envelope_item_get_ratelimiter_category(item); if (sentry__rate_limiter_is_disabled(rl, category)) { continue; } } sentry__envelope_serialize_item_into_stringbuilder(item, &sb); serialized_items += 1; } if (!serialized_items) { sentry__stringbuilder_cleanup(&sb); *size_out = 0; return NULL; } *size_out = sentry__stringbuilder_len(&sb); return sentry__stringbuilder_into_string(&sb); } char * sentry_envelope_serialize(const sentry_envelope_t *envelope, size_t *size_out) { sentry_stringbuilder_t sb; sentry__stringbuilder_init(&sb); sentry__envelope_serialize_into_stringbuilder(envelope, &sb); *size_out = sentry__stringbuilder_len(&sb); return sentry__stringbuilder_into_string(&sb); } MUST_USE int sentry_envelope_write_to_path( const sentry_envelope_t *envelope, const sentry_path_t *path) { // TODO: This currently builds the whole buffer in-memory. // It would be nice to actually stream this to a file. size_t buf_len = 0; char *buf = sentry_envelope_serialize(envelope, &buf_len); int rv = sentry__path_write_buffer(path, buf, buf_len); sentry_free(buf); return rv; } int sentry_envelope_write_to_file( const sentry_envelope_t *envelope, const char *path) { sentry_path_t *path_obj = sentry__path_from_str(path); int rv = sentry_envelope_write_to_path(envelope, path_obj); sentry__path_free(path_obj); return rv; } #ifdef SENTRY_UNITTEST size_t sentry__envelope_get_item_count(const sentry_envelope_t *envelope) { return envelope->is_raw ? 0 : envelope->contents.items.item_count; } const sentry_envelope_item_t * sentry__envelope_get_item(const sentry_envelope_t *envelope, size_t idx) { return !envelope->is_raw && idx < envelope->contents.items.item_count ? &envelope->contents.items.items[idx] : NULL; } sentry_value_t sentry__envelope_item_get_header( const sentry_envelope_item_t *item, const char *key) { return sentry_value_get_by_key(item->headers, key); } const char * sentry__envelope_item_get_payload( const sentry_envelope_item_t *item, size_t *payload_len_out) { if (payload_len_out) { *payload_len_out = item->payload_len; } return item->payload; } #endif