Source: evolution-data-server Version: 3.56.2-9 Severity: important Tags: security upstream patch
Hi, CVE-2026-110777: an event with an ATTACH property using a file URI can cause a local file to be read and uploaded to the calendar server by inlining it. The tracker lists this as unfixed and I found no Debian bug for it. Upstream fixed it in two commits on the gnome-51 branch: 2515ec687 and 15b3c521d. The second is 3d6d491ea on master, the commit the tracker names, so the tracker entry shows only the follow-up. The 3.62.1 tag contains only the first commit, so experimental's 3.62.1-2 may also need the second; I did not inspect that package. Attached is one squashed backport that applies to both 3.56.2-9 (forky/sid) and 3.56.2-0+deb13u1 (trixie) after their patch series, with no fuzz or offset. Its source changes are the upstream ones; commit 1's lines that commit 2 removes are dropped, and the test registration in tests/libecal/test-cal-utils.c moved because 3.56's main() differs. I compared the added lines against both upstream commits and found none that are not upstream's. Bookworm's 3.46.4-2 is not covered. The patch adds public API, with upstream's Since tags left as they are: libecal: e_cal_util_sanitize_untrusted(), e_cal_util_sanitize_untrusted_full(), ECalUtilUriNeedsChangeFunc libedata-cal: e_cal_backend_sanitize_untrusted() The new functions need adding to the symbols files, which the patch does not do. Testing is limited. Upstream's /ECalUtils/SanitizeUntrusted test passes on both versions, run in a small driver against the patched e-cal-util object. That function does not exist before the patch, so there is no before/after comparison. I did not build the full package, and did not reproduce the file upload. The backend and meta-backend changes were not exercised at run time, and test-cal-meta-backend was not run. Regards, Sumit Verma
Description: Calendar: limit which local files are inlined in components An event with an ATTACH property using a file:// URI could cause a local file to be read and uploaded to the calendar server by inlining it. Mangle file:// URIs of components coming from untrusted sources to xfile:// (keeping those in the backend's cache directory), and refuse to inline paths containing parts prefixed with "." or "etc" outside of the temporary and user cache directories. . Backported to 3.56: functional code from the two upstream commits applies unchanged; only the test registration in tests/libecal/test-cal-utils.c is reseated, as 3.56's main() does not contain the InitRecurTask test. . This adds public API: e_cal_util_sanitize_untrusted(), e_cal_util_sanitize_untrusted_full() and the ECalUtilUriNeedsChangeFunc type to libecal, and e_cal_backend_sanitize_untrusted() to libedata-cal. Origin: backport, https://gitlab.gnome.org/GNOME/evolution-data-server/-/commit/2515ec687c3fccbd70bd250161e25d10a60951fd https://gitlab.gnome.org/GNOME/evolution-data-server/-/commit/15b3c521dd16d6b6f822332291b33f4b4cbb2db9 Bug: https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/672 Author: Sumit Verma <[email protected]> Forwarded: not-needed Last-Update: 2026-11-08 --- --- a/src/calendar/backends/file/e-cal-backend-file.c +++ b/src/calendar/backends/file/e-cal-backend-file.c @@ -881,6 +881,7 @@ static void scan_vcalendar (ECalBackendFile *cbfile) { + ECalBackend *cal_backend = E_CAL_BACKEND (cbfile); ECalBackendFilePrivate *priv; ICalCompIter *iter; ICalComponent *icomp; @@ -907,6 +908,7 @@ g_object_ref (icomp); i_cal_object_set_owner (I_CAL_OBJECT (icomp), G_OBJECT (priv->vcalendar)); + e_cal_backend_sanitize_untrusted (cal_backend, icomp); check_dup_uid (cbfile, comp); add_component (cbfile, comp, FALSE); @@ -3478,6 +3480,7 @@ GError **error) { ESourceRegistry *registry; + ECalBackend *cal_backend; ECalBackendFile *cbfile; ECalBackendFilePrivate *priv; ECalClientTzlookupICalCompData *lookup_data = NULL; @@ -3490,6 +3493,7 @@ ECalBackendFileTzidData tzdata; GError *err = NULL; + cal_backend = E_CAL_BACKEND (backend); cbfile = E_CAL_BACKEND_FILE (backend); priv = cbfile->priv; @@ -3640,6 +3644,8 @@ if (!comp) continue; + e_cal_backend_sanitize_untrusted (cal_backend, subcomp); + /* Set the created and last modified times on the component, if not there already */ current = i_cal_time_new_current_with_zone (i_cal_timezone_get_utc_timezone ()); --- a/src/calendar/libecal/e-cal-util.c +++ b/src/calendar/libecal/e-cal-util.c @@ -3175,6 +3175,7 @@ GCancellable *cancellable, GError **error) { + GFile *cache_dir = NULL, *tmp_dir = NULL; ICalProperty *prop; const gchar *uid; gboolean success = TRUE; @@ -3196,53 +3197,94 @@ url_data = i_cal_attach_get_url (attach); url = url_data ? i_cal_value_decode_ical_string (url_data) : NULL; - if (url && g_str_has_prefix (url, "file://")) { + if (url && g_ascii_strncasecmp (url, "file://", 7) == 0) { GFile *file; - gchar *basename; - gchar *content; - gsize len; + gchar *path; + gboolean can; file = g_file_new_for_uri (url); - basename = g_file_get_basename (file); - if (g_file_load_contents (file, cancellable, &content, &len, NULL, error)) { - ICalAttach *new_attach; - ICalParameter *param; - gchar *base64; + path = g_file_get_path (file); + can = path && *path; - base64 = g_base64_encode ((const guchar *) content, len); - new_attach = i_cal_attach_new_from_data (base64, (GFunc) g_free, NULL); - g_free (content); + if (can) { + if (!tmp_dir) + tmp_dir = g_file_new_for_path (g_get_tmp_dir ()); + if (!cache_dir) + cache_dir = g_file_new_for_path (g_get_user_cache_dir ()); + + can = g_file_has_prefix (file, tmp_dir) || + g_file_has_prefix (file, cache_dir); + + if (!can) { + static const gchar *avoid_prefixes[] = { ".", "etc" }; + gchar **parts; + guint ii, jj, n_parts; + + parts = g_strsplit (path, G_DIR_SEPARATOR_S, -1); + n_parts = g_strv_length (parts); + can = TRUE; + + for (ii = 0; can && ii < G_N_ELEMENTS (avoid_prefixes); ii++) { + for (jj = 0; jj < n_parts; jj++) { + if (g_str_has_prefix (parts[jj], avoid_prefixes[ii])) { + can = FALSE; + break; + } + } + } - ecu_remove_all_but_filename_parameter (prop); + g_strfreev (parts); + } + } - i_cal_property_set_attach (prop, new_attach); - g_object_unref (new_attach); + if (can) { + gchar *basename; + gchar *content; + gsize len; + + basename = g_file_get_basename (file); + if (g_file_load_contents (file, cancellable, &content, &len, NULL, error)) { + ICalAttach *new_attach; + ICalParameter *param; + gchar *base64; + + base64 = g_base64_encode ((const guchar *) content, len); + new_attach = i_cal_attach_new_from_data (base64, (GFunc) g_free, NULL); + g_free (content); - param = i_cal_parameter_new_value (I_CAL_VALUE_BINARY); - i_cal_property_take_parameter (prop, param); + ecu_remove_all_but_filename_parameter (prop); - param = i_cal_parameter_new_encoding (I_CAL_ENCODING_BASE64); - i_cal_property_take_parameter (prop, param); + i_cal_property_set_attach (prop, new_attach); + g_object_unref (new_attach); - /* Preserve existing FILENAME parameter */ - if (!e_cal_util_property_has_parameter (prop, I_CAL_FILENAME_PARAMETER)) { - const gchar *use_filename = basename; - - /* generated filename by Evolution */ - if (uid && g_str_has_prefix (use_filename, uid) && - use_filename[strlen (uid)] == '-') { - use_filename += strlen (uid) + 1; - } + param = i_cal_parameter_new_value (I_CAL_VALUE_BINARY); + i_cal_property_take_parameter (prop, param); - param = i_cal_parameter_new_filename (use_filename); + param = i_cal_parameter_new_encoding (I_CAL_ENCODING_BASE64); i_cal_property_take_parameter (prop, param); + + /* Preserve existing FILENAME parameter */ + if (!e_cal_util_property_has_parameter (prop, I_CAL_FILENAME_PARAMETER)) { + const gchar *use_filename = basename; + + /* generated filename by Evolution */ + if (uid && g_str_has_prefix (use_filename, uid) && + use_filename[strlen (uid)] == '-') { + use_filename += strlen (uid) + 1; + } + + param = i_cal_parameter_new_filename (use_filename); + i_cal_property_take_parameter (prop, param); + } + } else { + success = FALSE; } - } else { - success = FALSE; + + g_free (basename); } g_object_unref (file); - g_free (basename); + g_free (path); } g_free (url); @@ -3252,6 +3294,8 @@ } g_clear_object (&prop); + g_clear_object (&cache_dir); + g_clear_object (&tmp_dir); return success; } @@ -4054,3 +4098,103 @@ return email; } + +/** + * e_cal_util_sanitize_untrusted_full: + * @comp: an #ICalComponent + * + * Sanitizes the @comp, which can be from an untrusted source, + * to not contain possible harmful properties or parameters. + * + * Note: This function changes API in the 3.64 series, + * matching e_cal_util_sanitize_untrusted_full(). + * + * Since: 3.62.1 + **/ +void +e_cal_util_sanitize_untrusted (ICalComponent *comp) +{ + g_return_if_fail (I_CAL_IS_COMPONENT (comp)); + + e_cal_util_sanitize_untrusted_full (comp, NULL, NULL); +} + +/** + * e_cal_util_sanitize_untrusted_full: + * @comp: an #ICalComponent + * @uri_needs_change_func: (nullable) (scope call) (closure user_data): an #ECalUtilUriNeedsChangeFunc + * @user_data: user data passed to the @uri_needs_change_func + * + * Sanitizes the @comp, which can be from an untrusted source, + * to not contain possible harmful properties or parameters. + * + * The @uri_needs_change_func, if not %NULL, is called for each + * attachment URI, to check whether the URI is trusted. It can + * be also used to replace the attachment URI-s. + * When the @uri_needs_change_func is %NULL the function modifies + * each "file://" URI to "xfile://". + * + * Note: This function is renamed to e_cal_util_sanitize_untrusted() in the 3.64 series. + * + * Since: 3.62.2 + **/ +void +e_cal_util_sanitize_untrusted_full (ICalComponent *comp, + ECalUtilUriNeedsChangeFunc uri_needs_change_func, + gpointer user_data) +{ + ICalProperty *prop; + + g_return_if_fail (I_CAL_IS_COMPONENT (comp)); + + for (prop = i_cal_component_get_first_property (comp, I_CAL_ATTACH_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (comp, I_CAL_ATTACH_PROPERTY)) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + if (attach && i_cal_attach_get_is_url (attach)) { + const gchar *url_data; + gchar *url = NULL; + + url_data = i_cal_attach_get_url (attach); + url = url_data ? i_cal_value_decode_ical_string (url_data) : NULL; + + if (url) { + gchar *new_url = NULL; + gboolean needs_change; + + if (uri_needs_change_func) { + needs_change = uri_needs_change_func (url, &new_url, user_data); + + if (needs_change && !new_url) + new_url = g_strconcat ("x", url, NULL); + } else { + needs_change = g_ascii_strncasecmp (url, "file://", 7) == 0; + + /* changes "file://" URI-s to "xfile://", which is not inlined */ + if (needs_change) + new_url = g_strconcat ("x", url, NULL); + } + + if (needs_change) { + ICalAttach *new_attach; + + new_attach = i_cal_attach_new_from_url (new_url); + + i_cal_property_set_attach (prop, new_attach); + + g_object_unref (new_attach); + } + + g_free (new_url); + } + + g_free (url); + } + + g_clear_object (&attach); + } + + g_clear_object (&prop); +} --- a/src/calendar/libecal/e-cal-util.h +++ b/src/calendar/libecal/e-cal-util.h @@ -493,6 +493,29 @@ const gchar * e_cal_util_get_attendee_email (const ECalComponentAttendee *attendee); const gchar * e_cal_util_get_property_email (ICalProperty *prop); +/** + * ECalUtilUriNeedsChangeFunc: + * @in_uri: a URI to check + * @out_uri: (nullable) (out) (transfer full): an out variable to optionally + * set a replacement URI for the @in_uri + * @user_data: user data passed to the e_cal_util_sanitize_untrusted_full() + * + * Called for each attachment URI, to check the @in_uri whether it needs to be replaced. + * The "file://" URI-s which need to replace will be changed to "xfile://" by + * the caller, unless the @out_uri is populated, in which case the @out_uri + * is used instead. + * + * Returns: %TRUE, when the @in_uri needs to be changed, or %FALSE, when it should stay as is + * + * Since: 3.62.2 + **/ +typedef gboolean (* ECalUtilUriNeedsChangeFunc) (const gchar *in_uri, + gchar **out_uri, + gpointer user_data); +void e_cal_util_sanitize_untrusted (ICalComponent *comp); +void e_cal_util_sanitize_untrusted_full(ICalComponent *comp, + ECalUtilUriNeedsChangeFunc uri_needs_change_func, + gpointer user_data); G_END_DECLS --- a/src/calendar/libedata-cal/e-cal-backend.c +++ b/src/calendar/libedata-cal/e-cal-backend.c @@ -4704,3 +4704,81 @@ cal_backend_dispatch_next_operation (cal_backend); } + +static gboolean +ecb_uri_needs_change_cb (const gchar *in_uri, + gchar **out_uri, + gpointer user_data) +{ + const gchar *cache_dir = user_data; + gboolean needs_change = FALSE; + + if (!in_uri) + return needs_change; + + if (g_ascii_strncasecmp (in_uri, "file://", 7) == 0) { + GFile *file; + + file = g_file_new_for_uri (in_uri); + + needs_change = !g_file_peek_path (file) || !cache_dir || !*cache_dir || !g_str_has_prefix (g_file_peek_path (file), cache_dir); + + g_clear_object (&file); + } else if (in_uri && cache_dir && g_ascii_strncasecmp (in_uri, "xfile://", 8) == 0) { + GFile *file; + + /* the local attachments in the backend cache could be incorrectly garbled, + thus fix them on the fly */ + file = g_file_new_for_uri (in_uri + 1); + + if (g_file_peek_path (file) && g_str_has_prefix (g_file_peek_path (file), cache_dir)) { + *out_uri = g_strdup (in_uri + 1); + needs_change = TRUE; + } + + g_clear_object (&file); + } + + return needs_change; +} + +/** + * e_cal_backend_sanitize_untrusted: + * @self: an #ECalBackend + * @comp: an #ICalComponent + * + * Sanitizes the @comp, which can be from an untrusted source, + * to not contain possible harmful properties or parameters. + * + * Unlike e_cal_util_sanitize_untrusted() this keeps attachment + * URI-s pointing to the @self cache directory untouched. + * + * Since: 3.62.2 + **/ +void +e_cal_backend_sanitize_untrusted (ECalBackend *self, + ICalComponent *comp) +{ + gchar *cache_dir = NULL; + + g_return_if_fail (E_IS_CAL_BACKEND (self)); + g_return_if_fail (I_CAL_IS_COMPONENT (comp)); + + g_mutex_lock (&self->priv->property_lock); + if (self->priv->cache_dir) { + guint len = strlen (self->priv->cache_dir); + + /* always end the cache_dir with the directory separator, to avoid prefix mismatches */ + if (len > 0 && self->priv->cache_dir[len - 1] == G_DIR_SEPARATOR) { + cache_dir = g_strdup (self->priv->cache_dir); + } else if (len > 0) { + cache_dir = g_malloc0 (sizeof (gchar) * (len + 2)); + g_warn_if_fail (g_snprintf (cache_dir, len + 2, "%s%c", self->priv->cache_dir, G_DIR_SEPARATOR) < len + 2); + } + } + g_mutex_unlock (&self->priv->property_lock); + + e_cal_util_sanitize_untrusted_full (comp, ecb_uri_needs_change_cb, cache_dir); + + g_free (cache_dir); +} --- a/src/calendar/libedata-cal/e-cal-backend.h +++ b/src/calendar/libedata-cal/e-cal-backend.h @@ -530,6 +530,9 @@ gpointer user_data, GDestroyNotify user_data_free); +void e_cal_backend_sanitize_untrusted(ECalBackend *self, + ICalComponent *comp); + G_END_DECLS #endif /* E_CAL_BACKEND_H */ --- a/src/calendar/libedata-cal/e-cal-meta-backend.c +++ b/src/calendar/libedata-cal/e-cal-meta-backend.c @@ -1162,6 +1162,7 @@ GError **error) { ECacheOfflineFlag offline_flag = E_CACHE_IS_ONLINE; + ECalBackend *cal_backend = E_CAL_BACKEND (meta_backend); ICalComponent *icomp = NULL; GSList *new_instances = NULL; gchar *extra = NULL; @@ -1199,6 +1200,7 @@ ECalComponent *comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (subcomp)); if (comp) { + e_cal_backend_sanitize_untrusted (cal_backend, e_cal_component_get_icalcomponent (comp)); new_instances = g_slist_prepend (new_instances, comp); if (!loaded_uid) @@ -1211,6 +1213,7 @@ icomp = NULL; if (comp) { + e_cal_backend_sanitize_untrusted (cal_backend, e_cal_component_get_icalcomponent (comp)); new_instances = g_slist_prepend (new_instances, comp); if (!loaded_uid) @@ -2590,6 +2593,8 @@ cal_backend = E_CAL_BACKEND (meta_backend); registry = e_cal_backend_get_registry (cal_backend); + e_cal_backend_sanitize_untrusted (cal_backend, e_cal_component_get_icalcomponent (comp)); + /* Just to check whether component exists in the cache */ is_in_cache = e_cal_cache_contains (cal_cache, e_cal_component_id_get_uid (id), NULL, E_CACHE_EXCLUDE_DELETED) || (e_cal_component_id_get_rid (id) && e_cal_cache_contains (cal_cache, e_cal_component_id_get_uid (id), e_cal_component_id_get_rid (id), E_CACHE_EXCLUDE_DELETED)); --- a/tests/libecal/test-cal-utils.c +++ b/tests/libecal/test-cal-utils.c @@ -355,6 +355,466 @@ g_clear_object (&new_comp); } +#define test_param(_name, _value) G_STMT_START { \ + gchar *param; \ + param = i_cal_property_get_parameter_as_string (prop, _name); \ + g_assert_cmpstr (param, ==, _value); \ + g_free (param); \ + } G_STMT_END + +static gboolean +test_sanitize_untrusted_uri_needs_change_cb (const gchar *in_uri, + gchar **out_uri, + gpointer user_data) +{ + const gchar *good_path = user_data; + gboolean needs_change = FALSE; + + if (!in_uri) + return needs_change; + + if (g_ascii_strncasecmp (in_uri, "file://", 7) == 0) { + needs_change = !g_str_has_prefix (in_uri + 7, good_path); + } else if (g_ascii_strncasecmp (in_uri, "xfile://", 8) == 0) { + needs_change = g_str_has_prefix (in_uri + 8, good_path); + if (needs_change) + *out_uri = g_strdup (in_uri + 1); + } + + return needs_change; +} + +static void +test_sanitize_untrusted (void) +{ + ICalComponent *icomp; + ICalProperty *prop; + guint nth_attach; + + /* first when all are fine */ + icomp = i_cal_component_new_from_string ( + "BEGIN:VEVENT\r\n" + "UID:1\r\n" + "SUMMARY:first\r\n" + "ATTACH;FILENAME=logo.png;FMTTYPE=image/png:https://gnome.org/logo.png\r\n" + "ATTACH;VALUE=BINARY;FILENAME=text.txt;ENCODING=BASE64:aGVsbG8K\r\n" + "ATTACH:https://gnome.org/\r\n" + "END:VEVENT\r\n"); + g_assert_nonnull (icomp); + + for (nth_attach = 0, prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "1"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "first"); + } else if (i_cal_property_isa (prop) == I_CAL_ATTACH_PROPERTY) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + g_assert_nonnull (attach); + + nth_attach++; + + switch (nth_attach) { + case 1: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "https://gnome.org/logo.png"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 2); + test_param ("filename", "logo.png"); + test_param ("fmttype", "image/png"); + break; + case 2: + g_assert_false (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_data (attach), ==, "aGVsbG8K"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 3); + test_param ("value", "BINARY"); + test_param ("filename", "text.txt"); + test_param ("encoding", "BASE64"); + break; + case 3: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "https://gnome.org/"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + default: + g_assert_not_reached (); + } + + g_clear_object (&attach); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + g_assert_cmpuint (nth_attach, ==, 3); + + e_cal_util_sanitize_untrusted_full (icomp, NULL, NULL); + + for (nth_attach = 0, prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "1"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "first"); + } else if (i_cal_property_isa (prop) == I_CAL_ATTACH_PROPERTY) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + g_assert_nonnull (attach); + + nth_attach++; + + switch (nth_attach) { + case 1: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "https://gnome.org/logo.png"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 2); + test_param ("filename", "logo.png"); + test_param ("fmttype", "image/png"); + break; + case 2: + g_assert_false (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_data (attach), ==, "aGVsbG8K"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 3); + test_param ("value", "BINARY"); + test_param ("filename", "text.txt"); + test_param ("encoding", "BASE64"); + break; + case 3: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "https://gnome.org/"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + default: + g_assert_not_reached (); + } + + g_clear_object (&attach); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + g_clear_object (&icomp); + g_assert_cmpuint (nth_attach, ==, 3); + + /* then when some are bad */ + icomp = i_cal_component_new_from_string ( + "BEGIN:VEVENT\r\n" + "UID:2\r\n" + "SUMMARY:second\r\n" + "ATTACH;FILENAME=logo.png;FMTTYPE=image/png:file:///tmp/image.png\r\n" + "ATTACH;VALUE=BINARY;FILENAME=text.txt;ENCODING=BASE64:aGVsbG8K\r\n" + "ATTACH:cid:part-id\r\n" + "ATTACH:FiLe://y.z\r\n" + "END:VEVENT\r\n"); + g_assert_nonnull (icomp); + + for (nth_attach = 0, prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "2"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "second"); + } else if (i_cal_property_isa (prop) == I_CAL_ATTACH_PROPERTY) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + g_assert_nonnull (attach); + + nth_attach++; + + switch (nth_attach) { + case 1: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "file:///tmp/image.png"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 2); + test_param ("filename", "logo.png"); + test_param ("fmttype", "image/png"); + break; + case 2: + g_assert_false (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_data (attach), ==, "aGVsbG8K"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 3); + test_param ("value", "BINARY"); + test_param ("filename", "text.txt"); + test_param ("encoding", "BASE64"); + break; + case 3: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "cid:part-id"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 4: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "FiLe://y.z"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + default: + g_assert_not_reached (); + } + + g_clear_object (&attach); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + g_assert_cmpuint (nth_attach, ==, 4); + + e_cal_util_sanitize_untrusted_full (icomp, NULL, NULL); + + for (nth_attach = 0, prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "2"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "second"); + } else if (i_cal_property_isa (prop) == I_CAL_ATTACH_PROPERTY) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + g_assert_nonnull (attach); + + nth_attach++; + + switch (nth_attach) { + case 1: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "xfile:///tmp/image.png"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 2); + test_param ("filename", "logo.png"); + test_param ("fmttype", "image/png"); + break; + case 2: + g_assert_false (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_data (attach), ==, "aGVsbG8K"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 3); + test_param ("value", "BINARY"); + test_param ("filename", "text.txt"); + test_param ("encoding", "BASE64"); + break; + case 3: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "cid:part-id"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 4: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "xFiLe://y.z"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + default: + g_assert_not_reached (); + } + + g_clear_object (&attach); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + g_clear_object (&icomp); + g_assert_cmpuint (nth_attach, ==, 4); + + /* then with no ATTACH */ + icomp = i_cal_component_new_from_string ( + "BEGIN:VEVENT\r\n" + "UID:3\r\n" + "SUMMARY:third\r\n" + "END:VEVENT\r\n"); + g_assert_nonnull (icomp); + + for (prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "3"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "third"); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + + e_cal_util_sanitize_untrusted_full (icomp, NULL, NULL); + + for (prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "3"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "third"); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + g_clear_object (&icomp); + + /* then flip the ATTACH URI-s */ + icomp = i_cal_component_new_from_string ( + "BEGIN:VEVENT\r\n" + "UID:4\r\n" + "SUMMARY:fourth\r\n" + "ATTACH:file:///good/path/file.txt\r\n" + "ATTACH:cid:part-id\r\n" + "ATTACH:file:///bad/path/bad.xml\r\n" + "END:VEVENT\r\n"); + g_assert_nonnull (icomp); + + for (nth_attach = 0, prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "4"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "fourth"); + } else if (i_cal_property_isa (prop) == I_CAL_ATTACH_PROPERTY) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + g_assert_nonnull (attach); + + nth_attach++; + + switch (nth_attach) { + case 1: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "file:///good/path/file.txt"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 2: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "cid:part-id"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 3: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "file:///bad/path/bad.xml"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + default: + g_assert_not_reached (); + } + + g_clear_object (&attach); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + g_assert_cmpuint (nth_attach, ==, 3); + + e_cal_util_sanitize_untrusted_full (icomp, test_sanitize_untrusted_uri_needs_change_cb, (gpointer) "/good/path/"); + + for (nth_attach = 0, prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "4"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "fourth"); + } else if (i_cal_property_isa (prop) == I_CAL_ATTACH_PROPERTY) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + g_assert_nonnull (attach); + + nth_attach++; + + switch (nth_attach) { + case 1: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "file:///good/path/file.txt"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 2: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "cid:part-id"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 3: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "xfile:///bad/path/bad.xml"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + default: + g_assert_not_reached (); + } + + g_clear_object (&attach); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + + e_cal_util_sanitize_untrusted_full (icomp, test_sanitize_untrusted_uri_needs_change_cb, (gpointer) "/bad/path/"); + + for (nth_attach = 0, prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); + prop; + g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { + if (i_cal_property_isa (prop) == I_CAL_UID_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_uid (prop), ==, "4"); + } else if (i_cal_property_isa (prop) == I_CAL_SUMMARY_PROPERTY) { + g_assert_cmpstr (i_cal_property_get_summary (prop), ==, "fourth"); + } else if (i_cal_property_isa (prop) == I_CAL_ATTACH_PROPERTY) { + ICalAttach *attach; + + attach = i_cal_property_get_attach (prop); + g_assert_nonnull (attach); + + nth_attach++; + + switch (nth_attach) { + case 1: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "xfile:///good/path/file.txt"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 2: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "cid:part-id"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + case 3: + g_assert_true (i_cal_attach_get_is_url (attach)); + g_assert_cmpstr (i_cal_attach_get_url (attach), ==, "file:///bad/path/bad.xml"); + g_assert_cmpint (i_cal_property_count_parameters (prop), ==, 0); + break; + default: + g_assert_not_reached (); + } + + g_clear_object (&attach); + } else { + g_assert_not_reached (); + } + } + + g_clear_object (&prop); + g_clear_object (&icomp); + g_assert_cmpuint (nth_attach, ==, 3); +} + +#undef test_param + gint main (gint argc, gchar **argv) @@ -366,6 +826,7 @@ e_test_server_utils_setup, test_clamp_vtimezone, e_test_server_utils_teardown); g_test_add ("/ECalUtils/Categories", ETestServerFixture, &test_closure, e_test_server_utils_setup, test_categories, e_test_server_utils_teardown); + g_test_add_func ("/ECalUtils/SanitizeUntrusted", test_sanitize_untrusted); return e_test_server_utils_run (argc, argv); }

