Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

**Features**:

- Add attachment support to user feedback ([#1414](https://github.com/getsentry/sentry-native/pull/1414))

## 0.11.3

**Features**:
Expand Down
29 changes: 29 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,35 @@ main(int argc, char **argv)

sentry_capture_feedback(user_feedback);
}
if (has_arg(argc, argv, "capture-user-feedback-with-attachment")) {
sentry_value_t user_feedback = sentry_value_new_feedback(
"some-message", "some-email", "some-name", NULL);

// Create a hint and attach both file and byte data
sentry_feedback_hint_t *hint = sentry_feedback_hint_new();

// Create a temporary file for the attachment
const char *attachment_path = ".sentry-test-feedback-attachment";
FILE *f = fopen(attachment_path, "w");
if (f) {
fprintf(f, "This is feedback attachment content");
fclose(f);
}

// Attach a file
sentry_feedback_hint_attach_file(hint, attachment_path);

// Attach bytes data (e.g., binary data from memory)
const char *binary_data = "binary attachment data";
sentry_feedback_hint_attach_bytes(
hint, binary_data, strlen(binary_data), "additional-info.txt");

// Capture feedback with attachments
sentry_capture_feedback_with_hint(user_feedback, hint);

// Clean up the temporary file
remove(attachment_path);
}
if (has_arg(argc, argv, "capture-user-report")) {
sentry_value_t event = sentry_value_new_message_event(
SENTRY_LEVEL_INFO, "my-logger", "Hello user feedback!");
Expand Down
67 changes: 67 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -2716,6 +2716,73 @@ SENTRY_API sentry_value_t sentry_value_new_feedback_n(const char *message,
*/
SENTRY_API void sentry_capture_feedback(sentry_value_t user_feedback);

/**
* A hint that can be passed to feedback capture to provide additional context,
* such as attachments.
*/
struct sentry_feedback_hint_s;
typedef struct sentry_feedback_hint_s sentry_feedback_hint_t;

/**
* Creates a new feedback hint.
*/
SENTRY_API sentry_feedback_hint_t *sentry_feedback_hint_new(void);

/**
* Attaches a file to a feedback hint.
*
* The file will be read and sent when the feedback is captured.
* Returns a pointer to the attachment, or NULL on error.
*/
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_file(
sentry_feedback_hint_t *hint, const char *path);
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_file_n(
sentry_feedback_hint_t *hint, const char *path, size_t path_len);

/**
* Attaches bytes to a feedback hint.
*
* The data is copied internally and will be sent when the feedback is captured.
* Returns a pointer to the attachment, or NULL on error.
*/
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_bytes(
sentry_feedback_hint_t *hint, const char *buf, size_t buf_len,
const char *filename);
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_bytes_n(
sentry_feedback_hint_t *hint, const char *buf, size_t buf_len,
const char *filename, size_t filename_len);

#ifdef SENTRY_PLATFORM_WINDOWS
/**
* Wide char version of `sentry_feedback_hint_attach_file`.
*/
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_filew(
sentry_feedback_hint_t *hint, const wchar_t *path);
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_filew_n(
sentry_feedback_hint_t *hint, const wchar_t *path, size_t path_len);

/**
* Wide char version of `sentry_feedback_hint_attach_bytes`.
*/
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_bytesw(
sentry_feedback_hint_t *hint, const char *buf, size_t buf_len,
const wchar_t *filename);
SENTRY_API sentry_attachment_t *sentry_feedback_hint_attach_bytesw_n(
sentry_feedback_hint_t *hint, const char *buf, size_t buf_len,
const wchar_t *filename, size_t filename_len);
#endif

/**
* Captures a manually created feedback with a hint and sends it to Sentry.
*
* This function takes ownership of both the feedback value and the hint,
* which will be freed automatically.
*
* The hint parameter can be NULL if no additional context is needed.
*/
SENTRY_API void sentry_capture_feedback_with_hint(
sentry_value_t user_feedback, sentry_feedback_hint_t *hint);

/**
* The status of a Span or Transaction.
*
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ sentry_target_sources_cwd(sentry
sentry_database.h
sentry_envelope.c
sentry_envelope.h
sentry_feedback.c
sentry_feedback.h
sentry_info.c
sentry_json.c
sentry_json.h
Expand Down
24 changes: 20 additions & 4 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "sentry_core.h"
#include "sentry_database.h"
#include "sentry_envelope.h"
#include "sentry_feedback.h"
#include "sentry_logs.h"
#include "sentry_options.h"
#include "sentry_path.h"
Expand Down Expand Up @@ -766,7 +767,8 @@ prepare_user_report(sentry_value_t user_report)
}

static sentry_envelope_t *
prepare_user_feedback(sentry_value_t user_feedback)
prepare_user_feedback(
sentry_value_t user_feedback, sentry_feedback_hint_t *hint)
{
sentry_envelope_t *envelope = NULL;

Expand All @@ -776,6 +778,10 @@ prepare_user_feedback(sentry_value_t user_feedback)
goto fail;
}

if (hint && hint->attachments) {
sentry__envelope_add_attachments(envelope, hint->attachments);
}

return envelope;

fail:
Expand Down Expand Up @@ -1483,17 +1489,27 @@ sentry_capture_user_feedback(sentry_value_t user_report)

void
sentry_capture_feedback(sentry_value_t user_feedback)
{
// Reuse the implementation with NULL hint
sentry_capture_feedback_with_hint(user_feedback, NULL);
}

void
sentry_capture_feedback_with_hint(
sentry_value_t user_feedback, sentry_feedback_hint_t *hint)
{
sentry_envelope_t *envelope = NULL;

SENTRY_WITH_OPTIONS (options) {
envelope = prepare_user_feedback(user_feedback);
envelope = prepare_user_feedback(user_feedback, hint);
if (envelope) {
sentry__capture_envelope(options->transport, envelope);
} else {
sentry_value_decref(user_feedback);
}
}

if (hint) {
sentry__feedback_hint_free(hint);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Feedback Capture Function Ownership Violation

The sentry_capture_feedback_with_hint function leaks the user_feedback value. It fails to decrement the value's reference count when sentry isn't initialized or when prepare_user_feedback successfully creates an envelope, violating its ownership contract.

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If envelope was created successfully it takes ownership of the user_feedback value so we don't have to decref manually.

}

bool
Expand Down
112 changes: 112 additions & 0 deletions src/sentry_feedback.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include "sentry_feedback.h"

#include "sentry_alloc.h"
#include "sentry_attachment.h"
#include "sentry_path.h"
#include "sentry_string.h"

#include <string.h>

sentry_feedback_hint_t *
sentry_feedback_hint_new(void)
{
sentry_feedback_hint_t *hint = SENTRY_MAKE(sentry_feedback_hint_t);
if (!hint) {
return NULL;
}
memset(hint, 0, sizeof(sentry_feedback_hint_t));
return hint;
}

void
sentry__feedback_hint_free(sentry_feedback_hint_t *hint)
{
if (!hint) {
return;
}
sentry__attachments_free(hint->attachments);
sentry_free(hint);
}

sentry_attachment_t *
sentry_feedback_hint_attach_file(sentry_feedback_hint_t *hint, const char *path)
{
return sentry_feedback_hint_attach_file_n(
hint, path, sentry__guarded_strlen(path));
}

sentry_attachment_t *
sentry_feedback_hint_attach_file_n(
sentry_feedback_hint_t *hint, const char *path, size_t path_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add_path(&hint->attachments,
sentry__path_from_str_n(path, path_len), ATTACHMENT, NULL);
}

sentry_attachment_t *
sentry_feedback_hint_attach_bytes(sentry_feedback_hint_t *hint, const char *buf,
size_t buf_len, const char *filename)
{
return sentry_feedback_hint_attach_bytes_n(
hint, buf, buf_len, filename, sentry__guarded_strlen(filename));
}

sentry_attachment_t *
sentry_feedback_hint_attach_bytes_n(sentry_feedback_hint_t *hint,
const char *buf, size_t buf_len, const char *filename, size_t filename_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add(&hint->attachments,
sentry__attachment_from_buffer(
buf, buf_len, sentry__path_from_str_n(filename, filename_len)),
ATTACHMENT, NULL);
}

#ifdef SENTRY_PLATFORM_WINDOWS
sentry_attachment_t *
sentry_feedback_hint_attach_filew(
sentry_feedback_hint_t *hint, const wchar_t *path)
{
size_t path_len = path ? wcslen(path) : 0;
return sentry_feedback_hint_attach_filew_n(hint, path, path_len);
}

sentry_attachment_t *
sentry_feedback_hint_attach_filew_n(
sentry_feedback_hint_t *hint, const wchar_t *path, size_t path_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add_path(&hint->attachments,
sentry__path_from_wstr_n(path, path_len), ATTACHMENT, NULL);
}

sentry_attachment_t *
sentry_feedback_hint_attach_bytesw(sentry_feedback_hint_t *hint,
const char *buf, size_t buf_len, const wchar_t *filename)
{
size_t filename_len = filename ? wcslen(filename) : 0;
return sentry_feedback_hint_attach_bytesw_n(
hint, buf, buf_len, filename, filename_len);
}

sentry_attachment_t *
sentry_feedback_hint_attach_bytesw_n(sentry_feedback_hint_t *hint,
const char *buf, size_t buf_len, const wchar_t *filename,
size_t filename_len)
{
if (!hint) {
return NULL;
}
return sentry__attachments_add(&hint->attachments,
sentry__attachment_from_buffer(
buf, buf_len, sentry__path_from_wstr_n(filename, filename_len)),
ATTACHMENT, NULL);
}
#endif
19 changes: 19 additions & 0 deletions src/sentry_feedback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef SENTRY_FEEDBACK_H_INCLUDED
#define SENTRY_FEEDBACK_H_INCLUDED

#include "sentry_boot.h"

/**
* A sentry Feedback Hint used to pass additional data along with a feedback
* when it's being captured.
*/
struct sentry_feedback_hint_s {
sentry_attachment_t *attachments;
};

/**
* Frees a feedback hint (internal use only).
*/
void sentry__feedback_hint_free(sentry_feedback_hint_t *hint);

#endif
34 changes: 34 additions & 0 deletions tests/test_integration_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,40 @@ def test_user_feedback_http(cmake, httpserver):
assert_user_feedback(envelope)


def test_user_feedback_with_attachments_http(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})

httpserver.expect_request(
"/api/123456/envelope/",
headers={"x-sentry-auth": auth_header},
).respond_with_data("OK")
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

run(
tmp_path,
"sentry_example",
["log", "capture-user-feedback-with-attachment"],
check=True,
env=env,
)

assert len(httpserver.log) == 1
output = httpserver.log[0][0].get_data()
envelope = Envelope.deserialize(output)

# Verify the feedback is present
assert_user_feedback(envelope)

# Verify attachments are present
attachment_count = 0
for item in envelope:
if item.headers.get("type") == "attachment":
attachment_count += 1

# Should have 2 attachments (one file, one bytes)
assert attachment_count == 2


def test_user_report_http(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"})

Expand Down
1 change: 1 addition & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_executable(sentry_test_unit
test_embedded_info.c
test_envelopes.c
test_failures.c
test_feedback.c
test_fuzzfailures.c
test_info.c
test_logger.c
Expand Down
Loading
Loading