From 4c3bb54f6035689056ccdf60b2e115b027619647 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 21 Jul 2025 16:43:32 +0200 Subject: [PATCH 01/17] feat: raw envelope headers --- include/sentry.h | 24 +++++++++++++ src/sentry_core.c | 8 +++++ src/sentry_envelope.c | 67 +++++++++++++++++++++++++++++++------ src/sentry_envelope.h | 4 +-- tests/unit/test_envelopes.c | 37 +++++++++++++++++++- tests/unit/tests.inc | 1 + 6 files changed, 128 insertions(+), 13 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 16d678bcc..d7c9e25de 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -662,6 +662,16 @@ typedef struct sentry_envelope_s sentry_envelope_t; */ SENTRY_API void sentry_envelope_free(sentry_envelope_t *envelope); +/** + * Given an Envelope, returns the header if present. + * + * This returns a borrowed value to the headers in the Envelope. + */ +SENTRY_API sentry_value_t sentry_envelope_get_header( + const sentry_envelope_t *envelope, const char *key); +SENTRY_API sentry_value_t sentry_envelope_get_header_n( + const sentry_envelope_t *envelope, const char *key, size_t key_len); + /** * Given an Envelope, returns the embedded Event if there is one. * @@ -698,6 +708,20 @@ SENTRY_API int sentry_envelope_write_to_file( SENTRY_API int sentry_envelope_write_to_file_n( const sentry_envelope_t *envelope, const char *path, size_t path_len); +/** + * Reads an envelope from a file. + * + * `path` is assumed to be in platform-specific filesystem path encoding. + */ +SENTRY_API sentry_envelope_t *sentry_envelope_read_from_file(const char *path); +SENTRY_API sentry_envelope_t *sentry_envelope_read_from_file_n( + const char *path, size_t path_len); + +/** + * Submits an envelope, first checking for consent. + */ +SENTRY_API void sentry_capture_envelope(sentry_envelope_t *envelope); + /** * The Sentry Client Options. * diff --git a/src/sentry_core.c b/src/sentry_core.c index 4a001451e..349f5fa47 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -447,6 +447,14 @@ sentry__capture_envelope( sentry__transport_send_envelope(transport, envelope); } +void +sentry_capture_envelope(sentry_envelope_t *envelope) +{ + SENTRY_WITH_OPTIONS (options) { + sentry__capture_envelope(options->transport, envelope); + } +} + static bool event_is_considered_error(sentry_value_t event) { diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index d2c4df73a..b6a65d07d 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -21,9 +21,9 @@ struct sentry_envelope_item_s { struct sentry_envelope_s { bool is_raw; + sentry_value_t headers; union { struct { - sentry_value_t headers; sentry_envelope_item_t items[SENTRY_MAX_ENVELOPE_ITEMS]; size_t item_count; } items; @@ -65,6 +65,36 @@ envelope_item_cleanup(sentry_envelope_item_t *item) sentry_free(item->payload); } +static void +envelope_parse_raw_headers(sentry_envelope_t *envelope) +{ + const char *newline = memchr(envelope->contents.raw.payload, '\n', + envelope->contents.raw.payload_len); + size_t headers_len = newline + ? (size_t)(newline - envelope->contents.raw.payload) + : envelope->contents.raw.payload_len; + envelope->headers + = sentry__value_from_json(envelope->contents.raw.payload, headers_len); +} + +sentry_value_t +sentry_envelope_get_header(const sentry_envelope_t *envelope, const char *key) +{ + return sentry_envelope_get_header_n( + envelope, key, sentry__guarded_strlen(key)); +} + +sentry_value_t +sentry_envelope_get_header_n( + const sentry_envelope_t *envelope, const char *key, size_t key_len) +{ + if (envelope->is_raw && sentry_value_is_null(envelope->headers)) { + envelope_parse_raw_headers((sentry_envelope_t *)envelope); + } + + return sentry_value_get_by_key_n(envelope->headers, key, key_len); +} + void sentry__envelope_item_set_header( sentry_envelope_item_t *item, const char *key, sentry_value_t value) @@ -116,12 +146,12 @@ sentry_envelope_free(sentry_envelope_t *envelope) if (!envelope) { return; } + sentry_value_decref(envelope->headers); 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]); } @@ -135,7 +165,7 @@ sentry__envelope_set_header( if (envelope->is_raw) { return; } - sentry_value_set_by_key(envelope->contents.items.headers, key, value); + sentry_value_set_by_key(envelope->headers, key, value); } sentry_envelope_t * @@ -148,7 +178,7 @@ sentry__envelope_new(void) rv->is_raw = false; rv->contents.items.item_count = 0; - rv->contents.items.headers = sentry_value_new_object(); + rv->headers = sentry_value_new_object(); SENTRY_WITH_OPTIONS (options) { if (options->dsn && options->dsn->is_valid) { @@ -181,6 +211,7 @@ sentry__envelope_from_path(const sentry_path_t *path) envelope->is_raw = true; envelope->contents.raw.payload = buf; envelope->contents.raw.payload_len = buf_len; + envelope->headers = sentry_value_new_null(); return envelope; } @@ -188,11 +219,8 @@ sentry__envelope_from_path(const sentry_path_t *path) 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_envelope_get_header(envelope, "event_id"))); } sentry_value_t @@ -562,7 +590,7 @@ sentry__envelope_serialize_headers_into_stringbuilder( { sentry_jsonwriter_t *jw = sentry__jsonwriter_new_sb(sb); if (jw) { - sentry__jsonwriter_write_value(jw, envelope->contents.items.headers); + sentry__jsonwriter_write_value(jw, envelope->headers); sentry__jsonwriter_free(jw); } } @@ -674,7 +702,7 @@ sentry_envelope_write_to_path( sentry_jsonwriter_t *jw = sentry__jsonwriter_new_fw(fw); if (jw) { - sentry__jsonwriter_write_value(jw, envelope->contents.items.headers); + sentry__jsonwriter_write_value(jw, envelope->headers); sentry__jsonwriter_reset(jw); for (size_t i = 0; i < envelope->contents.items.item_count; i++) { @@ -726,6 +754,25 @@ sentry_envelope_write_to_file( return sentry_envelope_write_to_file_n(envelope, path, strlen(path)); } +sentry_envelope_t * +sentry_envelope_read_from_file(const char *path) +{ + return sentry_envelope_read_from_file_n(path, sentry__guarded_strlen(path)); +} + +sentry_envelope_t * +sentry_envelope_read_from_file_n(const char *path, size_t path_len) +{ + sentry_path_t *path_obj = sentry__path_from_str_n(path, path_len); + if (!path_obj || path_len == 0) { + return NULL; + } + + sentry_envelope_t *envelope = sentry__envelope_from_path(path_obj); + sentry__path_free(path_obj); + return envelope; +} + #ifdef SENTRY_UNITTEST size_t sentry__envelope_get_item_count(const sentry_envelope_t *envelope) diff --git a/src/sentry_envelope.h b/src/sentry_envelope.h index a41985dce..ec908aee6 100644 --- a/src/sentry_envelope.h +++ b/src/sentry_envelope.h @@ -26,8 +26,8 @@ sentry_envelope_t *sentry__envelope_from_path(const sentry_path_t *path); /** * This returns the UUID of the event associated with this envelope. - * If there is no event inside this envelope, or the envelope was previously - * loaded from disk, the empty nil UUID will be returned. + * If there is no event inside this envelope, the empty nil UUID will be + * returned. */ sentry_uuid_t sentry__envelope_get_event_id(const sentry_envelope_t *envelope); diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index f264790c1..b60d80567 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -319,6 +319,7 @@ SENTRY_TEST(write_envelope_to_file_null) { sentry_envelope_t *empty_envelope = sentry__envelope_new(); + TEST_CHECK(!sentry_envelope_read_from_file(NULL)); TEST_CHECK_INT_EQUAL( sentry_envelope_write_to_file(NULL, "irrelevant/path"), 1); TEST_CHECK_INT_EQUAL( @@ -336,6 +337,7 @@ SENTRY_TEST(write_envelope_to_invalid_path) sentry_envelope_t *envelope = create_test_envelope(); const char *test_file_str = SENTRY_TEST_PATH_PREFIX "./directory_that_does_not_exist/sentry_test_envelope"; + TEST_CHECK(!sentry_envelope_read_from_file(test_file_str)); sentry_path_t *test_file_path = sentry__path_from_str(test_file_str); int rv = sentry_envelope_write_to_file(envelope, test_file_str); TEST_CHECK_INT_EQUAL(rv, 1); @@ -355,7 +357,7 @@ SENTRY_TEST(write_raw_envelope_to_file) sentry_envelope_write_to_file(envelope, test_file_str), 0); sentry_envelope_t *raw_envelope - = sentry__envelope_from_path(test_file_path); + = sentry_envelope_read_from_file(test_file_str); TEST_CHECK_INT_EQUAL( sentry_envelope_write_to_file(raw_envelope, test_file_str), 0); @@ -372,3 +374,36 @@ SENTRY_TEST(write_raw_envelope_to_file) sentry_envelope_free(raw_envelope); sentry_close(); } + +SENTRY_TEST(raw_envelope_headers) +{ + sentry_envelope_t *envelope = create_test_envelope(); + const char *test_file_str = SENTRY_TEST_PATH_PREFIX "sentry_test_envelope"; + sentry_path_t *test_file_path = sentry__path_from_str(test_file_str); + TEST_CHECK_INT_EQUAL( + sentry_envelope_write_to_file(envelope, test_file_str), 0); + + sentry_envelope_t *raw_envelope + = sentry_envelope_read_from_file(test_file_str); + + sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope); + char event_id_str[37]; + sentry_uuid_as_string(&event_id, event_id_str); + + sentry_uuid_t raw_event_id = sentry__envelope_get_event_id(raw_envelope); + char raw_event_id_str[37]; + sentry_uuid_as_string(&raw_event_id, raw_event_id_str); + TEST_CHECK_STRING_EQUAL(event_id_str, raw_event_id_str); + + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_envelope_get_header(raw_envelope, "dsn")), + "https://foo@sentry.invalid/42"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( + raw_envelope, "event_id")), + "c993afb6-b4ac-48a6-b61b-2558e601d65d"); + + sentry__path_free(test_file_path); + sentry_envelope_free(envelope); + sentry_envelope_free(raw_envelope); + sentry_close(); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 0afb3c9ce..d79e60d74 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -96,6 +96,7 @@ XX(path_relative_filename) XX(procmaps_parser) XX(propagation_context_init) XX(rate_limit_parsing) +XX(raw_envelope_headers) XX(recursive_paths) XX(ringbuffer_append) XX(ringbuffer_append_invalid_decref_value) From 4dec16eea80a8d9271b920ff1af81637424bac1b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 21 Jul 2025 17:04:48 +0200 Subject: [PATCH 02/17] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c642e22..f327db4d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Add `Dynamic Sampling Context (DSC)` to events. ([#1254](https://github.com/getsentry/sentry-native/pull/1254)) - Add `sentry_value_new_feedback` and `sentry_capture_feedback` to allow capturing [User Feedback](https://develop.sentry.dev/sdk/data-model/envelope-items/#user-feedback). ([#1304](https://github.com/getsentry/sentry-native/pull/1304)) - Deprecate `sentry_value_new_user_feedback` and `sentry_capture_user_feedback` in favor of the new API. +- Add `sentry_envelope_read_from_file`, `sentry_envelope_get_header`, and `sentry_capture_envelope`. ([#1320](https://github.com/getsentry/sentry-native/pull/1320)) **Fixes**: From 749cc236984d41706b14f3dcc82e711ce39c8083 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 21 Jul 2025 23:08:21 +0200 Subject: [PATCH 03/17] move parsing to sentry_envelope_read_from_file() --- src/sentry_envelope.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index b6a65d07d..0f8bd68f2 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -65,18 +65,6 @@ envelope_item_cleanup(sentry_envelope_item_t *item) sentry_free(item->payload); } -static void -envelope_parse_raw_headers(sentry_envelope_t *envelope) -{ - const char *newline = memchr(envelope->contents.raw.payload, '\n', - envelope->contents.raw.payload_len); - size_t headers_len = newline - ? (size_t)(newline - envelope->contents.raw.payload) - : envelope->contents.raw.payload_len; - envelope->headers - = sentry__value_from_json(envelope->contents.raw.payload, headers_len); -} - sentry_value_t sentry_envelope_get_header(const sentry_envelope_t *envelope, const char *key) { @@ -88,10 +76,6 @@ sentry_value_t sentry_envelope_get_header_n( const sentry_envelope_t *envelope, const char *key, size_t key_len) { - if (envelope->is_raw && sentry_value_is_null(envelope->headers)) { - envelope_parse_raw_headers((sentry_envelope_t *)envelope); - } - return sentry_value_get_by_key_n(envelope->headers, key, key_len); } @@ -769,6 +753,14 @@ sentry_envelope_read_from_file_n(const char *path, size_t path_len) } sentry_envelope_t *envelope = sentry__envelope_from_path(path_obj); + if (envelope) { + const char *newline = memchr(envelope->contents.raw.payload, '\n', + envelope->contents.raw.payload_len); + size_t headers_len = newline ? newline - envelope->contents.raw.payload + : envelope->contents.raw.payload_len; + envelope->headers = sentry__value_from_json( + envelope->contents.raw.payload, headers_len); + } sentry__path_free(path_obj); return envelope; } From 7527a0da6a53e0345b0fd91c18bae6d93abed016 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 21 Jul 2025 23:33:42 +0200 Subject: [PATCH 04/17] cast --- src/sentry_envelope.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 0f8bd68f2..00e18de36 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -756,8 +756,7 @@ sentry_envelope_read_from_file_n(const char *path, size_t path_len) if (envelope) { const char *newline = memchr(envelope->contents.raw.payload, '\n', envelope->contents.raw.payload_len); - size_t headers_len = newline ? newline - envelope->contents.raw.payload - : envelope->contents.raw.payload_len; + size_t headers_len = (size_t)(newline - envelope->contents.raw.payload); envelope->headers = sentry__value_from_json( envelope->contents.raw.payload, headers_len); } From 76af4300ae5998247d600444ebece88ed3f56f0f Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 22 Jul 2025 10:47:46 +0200 Subject: [PATCH 05/17] feat: parse whole envelope --- src/sentry_envelope.c | 125 ++++++++++++++++++++++++++++++++---- tests/unit/test_envelopes.c | 79 +++++++++++++++++------ tests/unit/tests.inc | 2 +- 3 files changed, 172 insertions(+), 34 deletions(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 00e18de36..3d15f210d 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -738,30 +738,127 @@ sentry_envelope_write_to_file( return sentry_envelope_write_to_file_n(envelope, path, strlen(path)); } +// https://develop.sentry.dev/sdk/data-model/envelopes/ +static sentry_envelope_t * +parse_envelope_from_file(sentry_path_t *path) +{ + if (!path) { + return NULL; + } + + size_t buf_len = 0; + char *buf = sentry__path_read_to_buffer(path, &buf_len); + sentry__path_free(path); + if (!buf) { + return NULL; + } + + sentry_envelope_t *envelope = sentry__envelope_new(); + if (!envelope) { + goto fail; + } + + const char *ptr = buf; + const char *end = buf + buf_len; + + // headers + const char *headers_end = memchr(ptr, '\n', end - ptr); + if (!headers_end) { + headers_end = end; + } + size_t headers_len = (size_t)(headers_end - ptr); + sentry_value_decref(envelope->headers); + envelope->headers = sentry__value_from_json(ptr, headers_len); + if (sentry_value_is_null(envelope->headers)) { + envelope->headers = sentry_value_new_object(); + } + + ptr = headers_end + 1; // skip newline + + // items + while (ptr < end) { + sentry_envelope_item_t *item = envelope_add_item(envelope); + if (!item) { + goto fail; + } + + // item headers + const char *item_headers_end = memchr(ptr, '\n', end - ptr); + if (!item_headers_end) { + item_headers_end = end; + } + size_t item_headers_len = (size_t)(item_headers_end - ptr); + sentry_value_decref(item->headers); + item->headers = sentry__value_from_json(ptr, item_headers_len); + if (sentry_value_is_null(item->headers)) { + goto fail; + } + ptr = item_headers_end + 1; // skip newline + + // item payload + sentry_value_t length + = sentry_value_get_by_key(item->headers, "length"); + if (sentry_value_is_null(length)) { + // find newline or end of buffer + const char *payload_end = memchr(ptr, '\n', end - ptr); + if (!payload_end) { + payload_end = end; + } + item->payload_len = (size_t)(payload_end - ptr); + } else { + item->payload_len = (size_t)sentry_value_as_int32(length); + } + if (item->payload_len <= 0 || ptr + item->payload_len > end) { + goto fail; + } + item->payload = sentry_malloc(item->payload_len + 1); + if (!item->payload) { + goto fail; + } + memcpy(item->payload, ptr, item->payload_len); + item->payload[item->payload_len] = '\0'; + + // item event/transaction + const char *type = sentry_value_as_string( + sentry_value_get_by_key(item->headers, "type")); + if (type + && (sentry__string_eq(type, "event") + || sentry__string_eq(type, "transaction"))) { + item->event + = sentry__value_from_json(item->payload, item->payload_len); + } + + ptr += item->payload_len; + while (ptr < end && *ptr == '\n') { + ptr++; + } + } + + sentry_free(buf); + return envelope; + +fail: + sentry_envelope_free(envelope); + sentry_free(buf); + return NULL; +} + sentry_envelope_t * sentry_envelope_read_from_file(const char *path) { - return sentry_envelope_read_from_file_n(path, sentry__guarded_strlen(path)); + if (!path) { + return NULL; + } + return parse_envelope_from_file(sentry__path_from_str(path)); } sentry_envelope_t * sentry_envelope_read_from_file_n(const char *path, size_t path_len) { - sentry_path_t *path_obj = sentry__path_from_str_n(path, path_len); - if (!path_obj || path_len == 0) { + if (!path || path_len == 0) { return NULL; } - - sentry_envelope_t *envelope = sentry__envelope_from_path(path_obj); - if (envelope) { - const char *newline = memchr(envelope->contents.raw.payload, '\n', - envelope->contents.raw.payload_len); - size_t headers_len = (size_t)(newline - envelope->contents.raw.payload); - envelope->headers = sentry__value_from_json( - envelope->contents.raw.payload, headers_len); - } - sentry__path_free(path_obj); - return envelope; + return parse_envelope_from_file(sentry__path_from_str_n(path, path_len)); } #ifdef SENTRY_UNITTEST diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index b60d80567..e01c250d0 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -14,7 +14,7 @@ static char *const SERIALIZED_ENVELOPE_STR "\"production\",\"sampled\":\"false\"}}\n" "{\"type\":\"event\",\"length\":71}\n" "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"some-" - "context\":null}\n" + "context\":1234}\n" "{\"type\":\"minidump\",\"length\":4}\n" "MDMP\n" "{\"type\":\"attachment\",\"length\":12}\n" @@ -263,7 +263,8 @@ create_test_envelope() sentry_value_t event = sentry_value_new_object(); sentry_value_set_by_key( event, "event_id", sentry__value_new_uuid(&event_id)); - sentry_value_set_by_key(event, "some-context", sentry_value_new_null()); + sentry_value_set_by_key( + event, "some-context", sentry_value_new_int32(1234)); sentry__envelope_add_event(envelope, event); char dmp[] = "MDMP"; @@ -357,7 +358,7 @@ SENTRY_TEST(write_raw_envelope_to_file) sentry_envelope_write_to_file(envelope, test_file_str), 0); sentry_envelope_t *raw_envelope - = sentry_envelope_read_from_file(test_file_str); + = sentry__envelope_from_path(test_file_path); TEST_CHECK_INT_EQUAL( sentry_envelope_write_to_file(raw_envelope, test_file_str), 0); @@ -375,35 +376,75 @@ SENTRY_TEST(write_raw_envelope_to_file) sentry_close(); } -SENTRY_TEST(raw_envelope_headers) +SENTRY_TEST(parse_envelope) { - sentry_envelope_t *envelope = create_test_envelope(); + sentry_envelope_t *test_envelope = create_test_envelope(); const char *test_file_str = SENTRY_TEST_PATH_PREFIX "sentry_test_envelope"; sentry_path_t *test_file_path = sentry__path_from_str(test_file_str); TEST_CHECK_INT_EQUAL( - sentry_envelope_write_to_file(envelope, test_file_str), 0); + sentry_envelope_write_to_file(test_envelope, test_file_str), 0); - sentry_envelope_t *raw_envelope + sentry_envelope_t *file_envelope = sentry_envelope_read_from_file(test_file_str); - sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( + file_envelope, "dsn")), + "https://foo@sentry.invalid/42"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( + file_envelope, "event_id")), + "c993afb6-b4ac-48a6-b61b-2558e601d65d"); + + sentry_uuid_t event_id = sentry__envelope_get_event_id(file_envelope); char event_id_str[37]; sentry_uuid_as_string(&event_id, event_id_str); + TEST_CHECK_STRING_EQUAL( + event_id_str, "c993afb6-b4ac-48a6-b61b-2558e601d65d"); + + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(file_envelope), 3); + + sentry_value_t event = sentry_envelope_get_event(file_envelope); + TEST_CHECK(!sentry_value_is_null(event)); + TEST_CHECK_INT_EQUAL( + sentry_value_as_int32(sentry_value_get_by_key(event, "some-context")), + 1234); - sentry_uuid_t raw_event_id = sentry__envelope_get_event_id(raw_envelope); - char raw_event_id_str[37]; - sentry_uuid_as_string(&raw_event_id, raw_event_id_str); - TEST_CHECK_STRING_EQUAL(event_id_str, raw_event_id_str); + const sentry_envelope_item_t *ev + = sentry__envelope_get_item(file_envelope, 0); + TEST_CHECK(!!ev); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry__envelope_item_get_header(ev, "type")), + "event"); + size_t ev_len = 0; + TEST_CHECK_STRING_EQUAL(sentry__envelope_item_get_payload(ev, &ev_len), + "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"," + "\"some-context\":1234}"); + const sentry_envelope_item_t *dmp + = sentry__envelope_get_item(file_envelope, 1); + TEST_CHECK(!!dmp); TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_envelope_get_header(raw_envelope, "dsn")), - "https://foo@sentry.invalid/42"); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( - raw_envelope, "event_id")), - "c993afb6-b4ac-48a6-b61b-2558e601d65d"); + sentry_value_as_string(sentry__envelope_item_get_header(dmp, "type")), + "minidump"); + size_t dmp_len = 0; + TEST_CHECK_STRING_EQUAL( + sentry__envelope_item_get_payload(dmp, &dmp_len), "MDMP"); + TEST_CHECK_INT_EQUAL(dmp_len, 4); + + const sentry_envelope_item_t *attachment + = sentry__envelope_get_item(file_envelope, 2); + TEST_CHECK(!!attachment); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry__envelope_item_get_header(attachment, "type")), + "attachment"); + size_t attachment_len = 0; + TEST_CHECK_STRING_EQUAL( + sentry__envelope_item_get_payload(attachment, &attachment_len), + "Hello World!"); + TEST_CHECK_INT_EQUAL(attachment_len, 12); sentry__path_free(test_file_path); - sentry_envelope_free(envelope); - sentry_envelope_free(raw_envelope); + sentry_envelope_free(test_envelope); + sentry_envelope_free(file_envelope); sentry_close(); } diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index d79e60d74..60b9ad0f7 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -85,6 +85,7 @@ XX(os_release_non_existent_files) XX(os_releases_snapshot) XX(overflow_spans) XX(page_allocator) +XX(parse_envelope) XX(path_basics) XX(path_current_exe) XX(path_directory) @@ -96,7 +97,6 @@ XX(path_relative_filename) XX(procmaps_parser) XX(propagation_context_init) XX(rate_limit_parsing) -XX(raw_envelope_headers) XX(recursive_paths) XX(ringbuffer_append) XX(ringbuffer_append_invalid_decref_value) From 4aacdb0cab6ae393dbdcd46d55af95043cb232fa Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 22 Jul 2025 11:27:28 +0200 Subject: [PATCH 06/17] Fix ClangCL warnings D:\a\sentry-native\sentry-native\src\sentry_envelope.c(765,53): error : implicit conversion changes signedness: 'long long' to 'unsigned long long' [-Werror,-Wsign-conversion] [C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\cmake0\sentry.vcxproj] D:\a\sentry-native\sentry-native\src\sentry_envelope.c(786,62): error : implicit conversion changes signedness: 'long long' to 'unsigned long long' [-Werror,-Wsign-conversion] [C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\cmake0\sentry.vcxproj] D:\a\sentry-native\sentry-native\src\sentry_envelope.c(803,61): error : implicit conversion changes signedness: 'long long' to 'unsigned long long' [-Werror,-Wsign-conversion] [C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\cmake0\sentry.vcxproj] --- src/sentry_envelope.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 3d15f210d..343315feb 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -762,7 +762,7 @@ parse_envelope_from_file(sentry_path_t *path) const char *end = buf + buf_len; // headers - const char *headers_end = memchr(ptr, '\n', end - ptr); + const char *headers_end = memchr(ptr, '\n', (size_t)(end - ptr)); if (!headers_end) { headers_end = end; } @@ -783,7 +783,7 @@ parse_envelope_from_file(sentry_path_t *path) } // item headers - const char *item_headers_end = memchr(ptr, '\n', end - ptr); + const char *item_headers_end = memchr(ptr, '\n', (size_t)(end - ptr)); if (!item_headers_end) { item_headers_end = end; } @@ -800,7 +800,7 @@ parse_envelope_from_file(sentry_path_t *path) = sentry_value_get_by_key(item->headers, "length"); if (sentry_value_is_null(length)) { // find newline or end of buffer - const char *payload_end = memchr(ptr, '\n', end - ptr); + const char *payload_end = memchr(ptr, '\n', (size_t)(end - ptr)); if (!payload_end) { payload_end = end; } From 9312d6f8cdd2979310bcd1c7f7a0b96c963a1687 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 22 Jul 2025 11:06:50 +0200 Subject: [PATCH 07/17] revert unnecessary changes --- src/sentry_envelope.c | 33 ++++++++++++++++++++------------- src/sentry_envelope.h | 4 ++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 343315feb..ebcdf57bd 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -21,9 +21,9 @@ struct sentry_envelope_item_s { struct sentry_envelope_s { bool is_raw; - sentry_value_t headers; union { struct { + sentry_value_t headers; sentry_envelope_item_t items[SENTRY_MAX_ENVELOPE_ITEMS]; size_t item_count; } items; @@ -76,7 +76,11 @@ sentry_value_t sentry_envelope_get_header_n( const sentry_envelope_t *envelope, const char *key, size_t key_len) { - return sentry_value_get_by_key_n(envelope->headers, key, key_len); + if (envelope->is_raw) { + return sentry_value_new_null(); + } + return sentry_value_get_by_key_n( + envelope->contents.items.headers, key, key_len); } void @@ -130,12 +134,12 @@ sentry_envelope_free(sentry_envelope_t *envelope) if (!envelope) { return; } - sentry_value_decref(envelope->headers); 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]); } @@ -149,7 +153,7 @@ sentry__envelope_set_header( if (envelope->is_raw) { return; } - sentry_value_set_by_key(envelope->headers, key, value); + sentry_value_set_by_key(envelope->contents.items.headers, key, value); } sentry_envelope_t * @@ -162,7 +166,7 @@ sentry__envelope_new(void) rv->is_raw = false; rv->contents.items.item_count = 0; - rv->headers = sentry_value_new_object(); + rv->contents.items.headers = sentry_value_new_object(); SENTRY_WITH_OPTIONS (options) { if (options->dsn && options->dsn->is_valid) { @@ -195,7 +199,6 @@ sentry__envelope_from_path(const sentry_path_t *path) envelope->is_raw = true; envelope->contents.raw.payload = buf; envelope->contents.raw.payload_len = buf_len; - envelope->headers = sentry_value_new_null(); return envelope; } @@ -203,8 +206,11 @@ sentry__envelope_from_path(const sentry_path_t *path) 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_envelope_get_header(envelope, "event_id"))); + sentry_value_get_by_key(envelope->contents.items.headers, "event_id"))); } sentry_value_t @@ -574,7 +580,7 @@ sentry__envelope_serialize_headers_into_stringbuilder( { sentry_jsonwriter_t *jw = sentry__jsonwriter_new_sb(sb); if (jw) { - sentry__jsonwriter_write_value(jw, envelope->headers); + sentry__jsonwriter_write_value(jw, envelope->contents.items.headers); sentry__jsonwriter_free(jw); } } @@ -686,7 +692,7 @@ sentry_envelope_write_to_path( sentry_jsonwriter_t *jw = sentry__jsonwriter_new_fw(fw); if (jw) { - sentry__jsonwriter_write_value(jw, envelope->headers); + sentry__jsonwriter_write_value(jw, envelope->contents.items.headers); sentry__jsonwriter_reset(jw); for (size_t i = 0; i < envelope->contents.items.item_count; i++) { @@ -767,10 +773,11 @@ parse_envelope_from_file(sentry_path_t *path) headers_end = end; } size_t headers_len = (size_t)(headers_end - ptr); - sentry_value_decref(envelope->headers); - envelope->headers = sentry__value_from_json(ptr, headers_len); - if (sentry_value_is_null(envelope->headers)) { - envelope->headers = sentry_value_new_object(); + sentry_value_decref(envelope->contents.items.headers); + envelope->contents.items.headers + = sentry__value_from_json(ptr, headers_len); + if (sentry_value_is_null(envelope->contents.items.headers)) { + envelope->contents.items.headers = sentry_value_new_object(); } ptr = headers_end + 1; // skip newline diff --git a/src/sentry_envelope.h b/src/sentry_envelope.h index ec908aee6..a41985dce 100644 --- a/src/sentry_envelope.h +++ b/src/sentry_envelope.h @@ -26,8 +26,8 @@ sentry_envelope_t *sentry__envelope_from_path(const sentry_path_t *path); /** * This returns the UUID of the event associated with this envelope. - * If there is no event inside this envelope, the empty nil UUID will be - * returned. + * If there is no event inside this envelope, or the envelope was previously + * loaded from disk, the empty nil UUID will be returned. */ sentry_uuid_t sentry__envelope_get_event_id(const sentry_envelope_t *envelope); From 028f14a0b4eb30ab4ae2833e31104426455a666c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 22 Jul 2025 12:17:55 +0200 Subject: [PATCH 08/17] Wide-string variant for Windows --- include/sentry.h | 14 ++++++++++++++ src/sentry_envelope.c | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/include/sentry.h b/include/sentry.h index d7c9e25de..b1377754e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -712,11 +712,25 @@ SENTRY_API int sentry_envelope_write_to_file_n( * Reads an envelope from a file. * * `path` is assumed to be in platform-specific filesystem path encoding. + * + * API Users on windows are encouraged to use `sentry_envelope_read_from_filew` + * instead. */ SENTRY_API sentry_envelope_t *sentry_envelope_read_from_file(const char *path); SENTRY_API sentry_envelope_t *sentry_envelope_read_from_file_n( const char *path, size_t path_len); +#ifdef SENTRY_PLATFORM_WINDOWS +/** + * Wide char versions of `sentry_envelope_read_from_file` and + * `sentry_envelope_read_from_file_n`. + */ +SENTRY_API sentry_envelope_t *sentry_envelope_read_from_filew( + const wchar_t *path); +SENTRY_API sentry_envelope_t *sentry_envelope_read_from_filew_n( + const wchar_t *path, size_t path_len); +#endif + /** * Submits an envelope, first checking for consent. */ diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index ebcdf57bd..81c5fa09f 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -868,6 +868,26 @@ sentry_envelope_read_from_file_n(const char *path, size_t path_len) return parse_envelope_from_file(sentry__path_from_str_n(path, path_len)); } +#ifdef SENTRY_PLATFORM_WINDOWS +sentry_envelope_t * +sentry_envelope_read_from_filew(const wchar_t *path) +{ + if (!path) { + return NULL; + } + return parse_envelope_from_file(sentry__path_from_wstr(path)); +} + +sentry_envelope_t * +sentry_envelope_read_from_filew_n(const wchar_t *path, size_t path_len) +{ + if (!path || path_len == 0) { + return NULL; + } + return parse_envelope_from_file(sentry__path_from_wstr_n(path, path_len)); +} +#endif + #ifdef SENTRY_UNITTEST size_t sentry__envelope_get_item_count(const sentry_envelope_t *envelope) From a9bf358e34d6198bb4401196bac3d235f81645aa Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 22 Jul 2025 12:48:52 +0200 Subject: [PATCH 09/17] Improve tests --- tests/unit/test_envelopes.c | 50 ++++++++++++++++++------------------- tests/unit/tests.inc | 4 +-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index e01c250d0..462381d8a 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -316,7 +316,7 @@ SENTRY_TEST(basic_write_envelope_to_file) sentry_close(); } -SENTRY_TEST(write_envelope_to_file_null) +SENTRY_TEST(read_write_envelope_to_file_null) { sentry_envelope_t *empty_envelope = sentry__envelope_new(); @@ -333,7 +333,7 @@ SENTRY_TEST(write_envelope_to_file_null) sentry_envelope_free(empty_envelope); } -SENTRY_TEST(write_envelope_to_invalid_path) +SENTRY_TEST(read_write_envelope_to_invalid_path) { sentry_envelope_t *envelope = create_test_envelope(); const char *test_file_str = SENTRY_TEST_PATH_PREFIX @@ -378,60 +378,61 @@ SENTRY_TEST(write_raw_envelope_to_file) SENTRY_TEST(parse_envelope) { - sentry_envelope_t *test_envelope = create_test_envelope(); const char *test_file_str = SENTRY_TEST_PATH_PREFIX "sentry_test_envelope"; sentry_path_t *test_file_path = sentry__path_from_str(test_file_str); TEST_CHECK_INT_EQUAL( - sentry_envelope_write_to_file(test_envelope, test_file_str), 0); + sentry__path_write_buffer(test_file_path, SERIALIZED_ENVELOPE_STR, + strlen(SERIALIZED_ENVELOPE_STR)), + 0); - sentry_envelope_t *file_envelope - = sentry_envelope_read_from_file(test_file_str); + sentry_envelope_t *envelope = sentry_envelope_read_from_file(test_file_str); + TEST_CHECK(!!envelope); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( - file_envelope, "dsn")), + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_envelope_get_header(envelope, "dsn")), "https://foo@sentry.invalid/42"); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( - file_envelope, "event_id")), + envelope, "event_id")), "c993afb6-b4ac-48a6-b61b-2558e601d65d"); - sentry_uuid_t event_id = sentry__envelope_get_event_id(file_envelope); + sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope); char event_id_str[37]; sentry_uuid_as_string(&event_id, event_id_str); TEST_CHECK_STRING_EQUAL( event_id_str, "c993afb6-b4ac-48a6-b61b-2558e601d65d"); - TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(file_envelope), 3); + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(envelope), 3); - sentry_value_t event = sentry_envelope_get_event(file_envelope); + sentry_value_t event = sentry_envelope_get_event(envelope); TEST_CHECK(!sentry_value_is_null(event)); TEST_CHECK_INT_EQUAL( sentry_value_as_int32(sentry_value_get_by_key(event, "some-context")), 1234); - const sentry_envelope_item_t *ev - = sentry__envelope_get_item(file_envelope, 0); - TEST_CHECK(!!ev); + const sentry_envelope_item_t *item = sentry__envelope_get_item(envelope, 0); + TEST_CHECK(!!item); TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry__envelope_item_get_header(ev, "type")), + sentry_value_as_string(sentry__envelope_item_get_header(item, "type")), "event"); size_t ev_len = 0; - TEST_CHECK_STRING_EQUAL(sentry__envelope_item_get_payload(ev, &ev_len), + TEST_CHECK_STRING_EQUAL(sentry__envelope_item_get_payload(item, &ev_len), "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"," "\"some-context\":1234}"); - const sentry_envelope_item_t *dmp - = sentry__envelope_get_item(file_envelope, 1); - TEST_CHECK(!!dmp); + const sentry_envelope_item_t *minidump + = sentry__envelope_get_item(envelope, 1); + TEST_CHECK(!!minidump); TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry__envelope_item_get_header(dmp, "type")), + sentry_value_as_string( + sentry__envelope_item_get_header(minidump, "type")), "minidump"); size_t dmp_len = 0; TEST_CHECK_STRING_EQUAL( - sentry__envelope_item_get_payload(dmp, &dmp_len), "MDMP"); + sentry__envelope_item_get_payload(minidump, &dmp_len), "MDMP"); TEST_CHECK_INT_EQUAL(dmp_len, 4); const sentry_envelope_item_t *attachment - = sentry__envelope_get_item(file_envelope, 2); + = sentry__envelope_get_item(envelope, 2); TEST_CHECK(!!attachment); TEST_CHECK_STRING_EQUAL( sentry_value_as_string( @@ -444,7 +445,6 @@ SENTRY_TEST(parse_envelope) TEST_CHECK_INT_EQUAL(attachment_len, 12); sentry__path_free(test_file_path); - sentry_envelope_free(test_envelope); - sentry_envelope_free(file_envelope); + sentry_envelope_free(envelope); sentry_close(); } diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 60b9ad0f7..c027ecf54 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -97,6 +97,8 @@ XX(path_relative_filename) XX(procmaps_parser) XX(propagation_context_init) XX(rate_limit_parsing) +XX(read_write_envelope_to_file_null) +XX(read_write_envelope_to_invalid_path) XX(recursive_paths) XX(ringbuffer_append) XX(ringbuffer_append_invalid_decref_value) @@ -188,6 +190,4 @@ XX(value_string_n) XX(value_unicode) XX(value_user) XX(value_wrong_type) -XX(write_envelope_to_file_null) -XX(write_envelope_to_invalid_path) XX(write_raw_envelope_to_file) From 89ffbba616da22c36986406440075d2ca71e868b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 06:14:08 +0200 Subject: [PATCH 10/17] size_t payload_len Co-authored-by: Ivan Dlugos <6349682+vaind@users.noreply.github.com> --- src/sentry_envelope.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 81c5fa09f..f048a971d 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -815,7 +815,7 @@ parse_envelope_from_file(sentry_path_t *path) } else { item->payload_len = (size_t)sentry_value_as_int32(length); } - if (item->payload_len <= 0 || ptr + item->payload_len > end) { + if (item->payload_len == 0 || ptr + item->payload_len > end) { goto fail; } item->payload = sentry_malloc(item->payload_len + 1); From b3d1795c65b753053ab7d4c64a91f8fde05a61b4 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 07:50:18 +0200 Subject: [PATCH 11/17] sentry_envelope_deserialize + tests --- include/sentry.h | 12 +- src/sentry_envelope.c | 88 ++++++++------ tests/unit/test_envelopes.c | 224 +++++++++++++++++++++++++++++++++++- tests/unit/tests.inc | 8 +- 4 files changed, 294 insertions(+), 38 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index b1377754e..f9260333c 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -709,7 +709,17 @@ SENTRY_API int sentry_envelope_write_to_file_n( const sentry_envelope_t *envelope, const char *path, size_t path_len); /** - * Reads an envelope from a file. + * De-serializes an envelope. + * + * The return value needs to be freed with sentry_envelope_free(). + * + * Returns NULL on failure. + */ +SENTRY_API sentry_envelope_t *sentry_envelope_deserialize( + const char *buf, size_t buf_len); + +/** + * De-serializes an envelope from a file. * * `path` is assumed to be in platform-specific filesystem path encoding. * diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index f048a971d..74e225862 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -745,17 +745,10 @@ sentry_envelope_write_to_file( } // https://develop.sentry.dev/sdk/data-model/envelopes/ -static sentry_envelope_t * -parse_envelope_from_file(sentry_path_t *path) +sentry_envelope_t * +sentry_envelope_deserialize(const char *buf, size_t buf_len) { - if (!path) { - return NULL; - } - - size_t buf_len = 0; - char *buf = sentry__path_read_to_buffer(path, &buf_len); - sentry__path_free(path); - if (!buf) { + if (!buf || buf_len == 0) { return NULL; } @@ -776,8 +769,9 @@ parse_envelope_from_file(sentry_path_t *path) sentry_value_decref(envelope->contents.items.headers); envelope->contents.items.headers = sentry__value_from_json(ptr, headers_len); - if (sentry_value_is_null(envelope->contents.items.headers)) { - envelope->contents.items.headers = sentry_value_new_object(); + if (sentry_value_get_type(envelope->contents.items.headers) + != SENTRY_VALUE_TYPE_OBJECT) { + goto fail; } ptr = headers_end + 1; // skip newline @@ -802,54 +796,78 @@ parse_envelope_from_file(sentry_path_t *path) } ptr = item_headers_end + 1; // skip newline + if (ptr > end) { + goto fail; + } + // item payload sentry_value_t length = sentry_value_get_by_key(item->headers, "length"); if (sentry_value_is_null(length)) { - // find newline or end of buffer + // length omitted -> find newline or end of buffer const char *payload_end = memchr(ptr, '\n', (size_t)(end - ptr)); if (!payload_end) { payload_end = end; } item->payload_len = (size_t)(payload_end - ptr); } else { - item->payload_len = (size_t)sentry_value_as_int32(length); - } - if (item->payload_len == 0 || ptr + item->payload_len > end) { - goto fail; - } - item->payload = sentry_malloc(item->payload_len + 1); - if (!item->payload) { - goto fail; + int payload_len = sentry_value_as_int32(length); + if (payload_len < 0) { + goto fail; + } + item->payload_len = (size_t)payload_len; } - memcpy(item->payload, ptr, item->payload_len); - item->payload[item->payload_len] = '\0'; - - // item event/transaction - const char *type = sentry_value_as_string( - sentry_value_get_by_key(item->headers, "type")); - if (type - && (sentry__string_eq(type, "event") - || sentry__string_eq(type, "transaction"))) { - item->event - = sentry__value_from_json(item->payload, item->payload_len); + if (item->payload_len > 0) { + if (ptr + item->payload_len > end) { + goto fail; + } + item->payload = sentry_malloc(item->payload_len + 1); + if (!item->payload) { + goto fail; + } + memcpy(item->payload, ptr, item->payload_len); + item->payload[item->payload_len] = '\0'; + + // item event/transaction + const char *type = sentry_value_as_string( + sentry_value_get_by_key(item->headers, "type")); + if (type + && (sentry__string_eq(type, "event") + || sentry__string_eq(type, "transaction"))) { + item->event + = sentry__value_from_json(item->payload, item->payload_len); + } + + ptr += item->payload_len; } - ptr += item->payload_len; while (ptr < end && *ptr == '\n') { ptr++; } } - sentry_free(buf); return envelope; fail: sentry_envelope_free(envelope); - sentry_free(buf); return NULL; } +static sentry_envelope_t * +parse_envelope_from_file(sentry_path_t *path) +{ + if (!path) { + return NULL; + } + + size_t buf_len = 0; + char *buf = sentry__path_read_to_buffer(path, &buf_len); + sentry_envelope_t *envelope = sentry_envelope_deserialize(buf, buf_len); + sentry_free(buf); + sentry__path_free(path); + return envelope; +} + sentry_envelope_t * sentry_envelope_read_from_file(const char *path) { diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 462381d8a..935351233 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -376,7 +376,7 @@ SENTRY_TEST(write_raw_envelope_to_file) sentry_close(); } -SENTRY_TEST(parse_envelope) +SENTRY_TEST(read_envelope_from_file) { const char *test_file_str = SENTRY_TEST_PATH_PREFIX "sentry_test_envelope"; sentry_path_t *test_file_path = sentry__path_from_str(test_file_str); @@ -448,3 +448,225 @@ SENTRY_TEST(parse_envelope) sentry_envelope_free(envelope); sentry_close(); } + +SENTRY_TEST(deserialize_envelope) +{ + const char *buf + = "{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\",\"dsn\":\"https:/" + "/e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42\"}\n" + "{\"type\":\"attachment\",\"length\":10,\"content_type\":\"text/" + "plain\",\"filename\":\"hello.txt\"}\n" + "\xef\xbb\xbfHello\r\n\n" + "{\"type\":\"event\",\"length\":41,\"content_type\":\"application/" + "json\",\"filename\":\"application.log\"}\n" + "{\"message\":\"hello world\",\"level\":\"error\"}\n"; + + sentry_envelope_t *envelope = sentry_envelope_deserialize(buf, strlen(buf)); + TEST_CHECK(!!envelope); + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(envelope), 2); + + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_envelope_get_header(envelope, "dsn")), + "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( + envelope, "event_id")), + "9ec79c33ec9942ab8353589fcb2e04dc"); + TEST_CHECK_JSON_VALUE(sentry_envelope_get_event(envelope), + "{\"message\":\"hello world\",\"level\":\"error\"}"); + + const sentry_envelope_item_t *attachment + = sentry__envelope_get_item(envelope, 0); + TEST_CHECK(!!attachment); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry__envelope_item_get_header(attachment, "type")), + "attachment"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry__envelope_item_get_header(attachment, "filename")), + "hello.txt"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry__envelope_item_get_header(attachment, "content_type")), + "text/plain"); + size_t attachment_len = 0; + TEST_CHECK_STRING_EQUAL( + sentry__envelope_item_get_payload(attachment, &attachment_len), + "\xef\xbb\xbfHello\r\n"); + TEST_CHECK_INT_EQUAL(attachment_len, 10); + + sentry_envelope_free(envelope); +} + +static void +test_deserialize_envelope_empty_attachments(const char *buf, size_t buf_len) +{ + sentry_envelope_t *envelope = sentry_envelope_deserialize(buf, buf_len); + TEST_CHECK(!!envelope); + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(envelope), 2); + + TEST_CHECK( + sentry_value_is_null(sentry_envelope_get_header(envelope, "dsn"))); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( + envelope, "event_id")), + "9ec79c33ec9942ab8353589fcb2e04dc"); + TEST_CHECK(sentry_value_is_null(sentry_envelope_get_event(envelope))); + + for (int i = 0; i < 2; ++i) { + const sentry_envelope_item_t *attachment + = sentry__envelope_get_item(envelope, i); + TEST_CHECK(!!attachment); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry__envelope_item_get_header(attachment, "type")), + "attachment"); + TEST_CHECK_INT_EQUAL( + sentry_value_as_int32( + sentry__envelope_item_get_header(attachment, "length")), + 0); + size_t attachment_len = 0; + TEST_CHECK( + !sentry__envelope_item_get_payload(attachment, &attachment_len)); + TEST_CHECK_INT_EQUAL(attachment_len, 0); + } + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(deserialize_envelope_empty_attachments) +{ + const char *buf = "{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n" + "{\"type\":\"attachment\",\"length\":0}\n" + "\n" + "{\"type\":\"attachment\",\"length\":0}\n" + "\n"; + size_t buf_len = strlen(buf); + + // trailing newline + test_deserialize_envelope_empty_attachments(buf, buf_len); + + // eof + test_deserialize_envelope_empty_attachments(buf, buf_len - 1); +} + +static void +test_deserialize_envelope_implicit_length(const char *buf, size_t buf_len) +{ + sentry_envelope_t *envelope = sentry_envelope_deserialize(buf, buf_len); + TEST_CHECK(!!envelope); + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(envelope), 1); + + TEST_CHECK( + sentry_value_is_null(sentry_envelope_get_header(envelope, "dsn"))); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_envelope_get_header( + envelope, "event_id")), + "9ec79c33ec9942ab8353589fcb2e04dc"); + TEST_CHECK(sentry_value_is_null(sentry_envelope_get_event(envelope))); + + const sentry_envelope_item_t *attachment + = sentry__envelope_get_item(envelope, 0); + TEST_CHECK(!!attachment); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry__envelope_item_get_header(attachment, "type")), + "attachment"); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(sentry__envelope_item_get_header( + attachment, "length")), + 0); + size_t attachment1_len = 0; + TEST_CHECK_STRING_EQUAL( + sentry__envelope_item_get_payload(attachment, &attachment1_len), + "helloworld"); + TEST_CHECK_INT_EQUAL(attachment1_len, 10); + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(deserialize_envelope_implicit_length) +{ + const char *buf = "{\"event_id\":\"9ec79c33ec9942ab8353589fcb2e04dc\"}\n" + "{\"type\":\"attachment\"}\n" + "helloworld\n"; + size_t buf_len = strlen(buf); + + // trailing newline + test_deserialize_envelope_implicit_length(buf, buf_len); + + // eof + test_deserialize_envelope_implicit_length(buf, buf_len - 1); +} + +SENTRY_TEST(deserialize_envelope_no_headers) +{ + const char *session = "{\"started\": " + "\"2020-02-07T14:16:00Z\",\"attrs\":{\"release\":" + "\"sentry-test@1.0.0\"}" + "}"; + char buf[512]; + snprintf(buf, sizeof(buf), + "{}\n" + "{\"type\":\"session\"}\n" + "%s", + session); + + sentry_envelope_t *envelope = sentry_envelope_deserialize(buf, strlen(buf)); + TEST_CHECK(!!envelope); + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(envelope), 1); + + TEST_CHECK( + sentry_value_is_null(sentry_envelope_get_header(envelope, "dsn"))); + TEST_CHECK( + sentry_value_is_null(sentry_envelope_get_header(envelope, "event_id"))); + TEST_CHECK(sentry_value_is_null(sentry_envelope_get_event(envelope))); + + const sentry_envelope_item_t *item = sentry__envelope_get_item(envelope, 0); + TEST_CHECK(!!item); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry__envelope_item_get_header(item, "type")), + "session"); + TEST_CHECK( + sentry_value_is_null(sentry__envelope_item_get_header(item, "length"))); + size_t session_len = 0; + TEST_CHECK_STRING_EQUAL( + sentry__envelope_item_get_payload(item, &session_len), session); + TEST_CHECK_INT_EQUAL(session_len, strlen(session)); + + sentry_envelope_free(envelope); +} + +static void +test_deserialize_envelope_empty(const char *buf, size_t buf_len) +{ + sentry_envelope_t *envelope = sentry_envelope_deserialize(buf, buf_len); + TEST_CHECK(!!envelope); + TEST_CHECK_INT_EQUAL(sentry__envelope_get_item_count(envelope), 0); + + TEST_CHECK( + sentry_value_is_null(sentry_envelope_get_header(envelope, "dsn"))); + TEST_CHECK( + sentry_value_is_null(sentry_envelope_get_header(envelope, "event_id"))); + TEST_CHECK(sentry_value_is_null(sentry_envelope_get_event(envelope))); + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(deserialize_envelope_empty) +{ + const char *buf = "{}\n"; + size_t buf_len = strlen(buf); + + // trailing newline + test_deserialize_envelope_empty(buf, buf_len); + + // eof + test_deserialize_envelope_empty(buf, buf_len - 1); +} + +SENTRY_TEST(deserialize_envelope_invalid) +{ + TEST_CHECK(!sentry_envelope_deserialize("", 0)); + TEST_CHECK(!sentry_envelope_deserialize("{}", 0)); + TEST_CHECK(!sentry_envelope_deserialize("\n", 1)); + TEST_CHECK(!sentry_envelope_deserialize("{}\n{}", 5)); + TEST_CHECK(!sentry_envelope_deserialize("invalid", 7)); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index c027ecf54..7ddb80392 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -37,6 +37,12 @@ XX(count_sampled_events) XX(crash_marker) XX(crashed_last_run) XX(custom_logger) +XX(deserialize_envelope) +XX(deserialize_envelope_empty) +XX(deserialize_envelope_empty_attachments) +XX(deserialize_envelope_implicit_length) +XX(deserialize_envelope_invalid) +XX(deserialize_envelope_no_headers) XX(discarding_before_send) XX(distributed_headers) XX(distributed_headers_invalid_spanid) @@ -85,7 +91,6 @@ XX(os_release_non_existent_files) XX(os_releases_snapshot) XX(overflow_spans) XX(page_allocator) -XX(parse_envelope) XX(path_basics) XX(path_current_exe) XX(path_directory) @@ -97,6 +102,7 @@ XX(path_relative_filename) XX(procmaps_parser) XX(propagation_context_init) XX(rate_limit_parsing) +XX(read_envelope_from_file) XX(read_write_envelope_to_file_null) XX(read_write_envelope_to_invalid_path) XX(recursive_paths) From d7f148dbe2a0a3af999bb266e816740a2b6df659 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 07:58:15 +0200 Subject: [PATCH 12/17] validate headers --- src/sentry_envelope.c | 2 +- tests/unit/test_envelopes.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 74e225862..b0b63d9d3 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -791,7 +791,7 @@ sentry_envelope_deserialize(const char *buf, size_t buf_len) size_t item_headers_len = (size_t)(item_headers_end - ptr); sentry_value_decref(item->headers); item->headers = sentry__value_from_json(ptr, item_headers_len); - if (sentry_value_is_null(item->headers)) { + if (sentry_value_get_type(item->headers) != SENTRY_VALUE_TYPE_OBJECT) { goto fail; } ptr = item_headers_end + 1; // skip newline diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 935351233..61d5980b3 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -668,5 +668,6 @@ SENTRY_TEST(deserialize_envelope_invalid) TEST_CHECK(!sentry_envelope_deserialize("{}", 0)); TEST_CHECK(!sentry_envelope_deserialize("\n", 1)); TEST_CHECK(!sentry_envelope_deserialize("{}\n{}", 5)); + TEST_CHECK(!sentry_envelope_deserialize("{}\ninvalid\n", 11)); TEST_CHECK(!sentry_envelope_deserialize("invalid", 7)); } From 68f20d237e31704d11a3d052982a3ba2baaae295 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 08:42:31 +0200 Subject: [PATCH 13/17] don't advance ptr past the buffer even if it's never used might make cursor satisfied? --- src/sentry_envelope.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index b0b63d9d3..ab7f1e8ff 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -774,7 +774,10 @@ sentry_envelope_deserialize(const char *buf, size_t buf_len) goto fail; } - ptr = headers_end + 1; // skip newline + ptr = headers_end; + if (ptr < end) { + ptr++; // skip newline + } // items while (ptr < end) { From 243094d59f8bb160e0d9928566a8f7df4ae25c80 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 08:47:12 +0200 Subject: [PATCH 14/17] sentry_capture_envelope: add extra null check --- src/sentry_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sentry_core.c b/src/sentry_core.c index 349f5fa47..d2e14593e 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -450,6 +450,9 @@ sentry__capture_envelope( void sentry_capture_envelope(sentry_envelope_t *envelope) { + if (!envelope) { + return; + } SENTRY_WITH_OPTIONS (options) { sentry__capture_envelope(options->transport, envelope); } From 91b92e281da38f72ac02a8d7f02c8e270faa0890 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 08:51:36 +0200 Subject: [PATCH 15/17] sentry_envelope_get_header_n: add null check --- src/sentry_envelope.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index ab7f1e8ff..6f69b7514 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -76,7 +76,7 @@ sentry_value_t sentry_envelope_get_header_n( const sentry_envelope_t *envelope, const char *key, size_t key_len) { - if (envelope->is_raw) { + if (!envelope || envelope->is_raw) { return sentry_value_new_null(); } return sentry_value_get_by_key_n( From 41b42271adfb1bf50c54ed69ba8284d7e9d2df36 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 09:11:33 +0200 Subject: [PATCH 16/17] prevent overflow --- src/sentry_envelope.c | 4 +++- tests/unit/test_envelopes.c | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 6f69b7514..0afb86769 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -10,6 +10,7 @@ #include "sentry_transport.h" #include "sentry_value.h" #include +#include #include struct sentry_envelope_item_s { @@ -821,7 +822,8 @@ sentry_envelope_deserialize(const char *buf, size_t buf_len) item->payload_len = (size_t)payload_len; } if (item->payload_len > 0) { - if (ptr + item->payload_len > end) { + if (ptr + item->payload_len > end + || item->payload_len > INT32_MAX - 1) { goto fail; } item->payload = sentry_malloc(item->payload_len + 1); diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 61d5980b3..20a4230fa 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -670,4 +670,8 @@ SENTRY_TEST(deserialize_envelope_invalid) TEST_CHECK(!sentry_envelope_deserialize("{}\n{}", 5)); TEST_CHECK(!sentry_envelope_deserialize("{}\ninvalid\n", 11)); TEST_CHECK(!sentry_envelope_deserialize("invalid", 7)); + TEST_CHECK(!sentry_envelope_deserialize("{}\n{\"length\":-1}\n", 17)); + char buf[128]; + snprintf(buf, sizeof(buf), "{}\n{\"length\":%d}\n", INT32_MAX); + TEST_CHECK(!sentry_envelope_deserialize(buf, strlen(buf))); } From fb54090e604e44edd2732f6d81ce8a77261af5b8 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 25 Jul 2025 10:16:16 +0200 Subject: [PATCH 17/17] add TODO comments for sentry_value_as_uint64 + SIZE_MAX --- src/sentry_envelope.c | 4 ++++ tests/unit/test_envelopes.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 0afb86769..3fc69c188 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -815,6 +815,8 @@ sentry_envelope_deserialize(const char *buf, size_t buf_len) } item->payload_len = (size_t)(payload_end - ptr); } else { + // TODO: sentry_value_as_uint64 + // https://github.com/getsentry/sentry-native/pull/1301 int payload_len = sentry_value_as_int32(length); if (payload_len < 0) { goto fail; @@ -822,6 +824,8 @@ sentry_envelope_deserialize(const char *buf, size_t buf_len) item->payload_len = (size_t)payload_len; } if (item->payload_len > 0) { + // TODO: SIZE_MAX + // https://github.com/getsentry/sentry-native/pull/1301 if (ptr + item->payload_len > end || item->payload_len > INT32_MAX - 1) { goto fail; diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 20a4230fa..24594dbb5 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -671,6 +671,8 @@ SENTRY_TEST(deserialize_envelope_invalid) TEST_CHECK(!sentry_envelope_deserialize("{}\ninvalid\n", 11)); TEST_CHECK(!sentry_envelope_deserialize("invalid", 7)); TEST_CHECK(!sentry_envelope_deserialize("{}\n{\"length\":-1}\n", 17)); + // TODO: SIZE_MAX + // https://github.com/getsentry/sentry-native/pull/1301 char buf[128]; snprintf(buf, sizeof(buf), "{}\n{\"length\":%d}\n", INT32_MAX); TEST_CHECK(!sentry_envelope_deserialize(buf, strlen(buf)));