Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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