/* * e-source-mail-signature.c * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /** * SECTION: e-source-mail-signature * @include: libedataserver/libedataserver.h * @short_description: #ESource extension for email signatures * * The #ESourceMailSignature extension refers to a personalized email * signature. * * Access the extension as follows: * * |[ * #include * * ESourceMailSignature *extension; * * extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_SIGNATURE); * ]| **/ #include "evolution-data-server-config.h" #include #include #include #include "e-source-mail-signature.h" typedef struct _AsyncContext AsyncContext; struct _ESourceMailSignaturePrivate { GFile *file; gchar *mime_type; }; struct _AsyncContext { gchar *contents; gchar *symlink_target; gsize length; }; enum { PROP_0, PROP_FILE, PROP_MIME_TYPE }; G_DEFINE_TYPE_WITH_PRIVATE ( ESourceMailSignature, e_source_mail_signature, E_TYPE_SOURCE_EXTENSION) static void async_context_free (AsyncContext *async_context) { g_free (async_context->contents); g_free (async_context->symlink_target); g_slice_free (AsyncContext, async_context); } static void source_mail_signature_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_MIME_TYPE: e_source_mail_signature_set_mime_type ( E_SOURCE_MAIL_SIGNATURE (object), g_value_get_string (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void source_mail_signature_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_FILE: g_value_set_object ( value, e_source_mail_signature_get_file ( E_SOURCE_MAIL_SIGNATURE (object))); return; case PROP_MIME_TYPE: g_value_take_string ( value, e_source_mail_signature_dup_mime_type ( E_SOURCE_MAIL_SIGNATURE (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void source_mail_signature_dispose (GObject *object) { ESourceMailSignaturePrivate *priv; priv = E_SOURCE_MAIL_SIGNATURE (object)->priv; g_clear_object (&priv->file); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_source_mail_signature_parent_class)-> dispose (object); } static void source_mail_signature_finalize (GObject *object) { ESourceMailSignaturePrivate *priv; priv = E_SOURCE_MAIL_SIGNATURE (object)->priv; g_free (priv->mime_type); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_source_mail_signature_parent_class)-> finalize (object); } static void source_mail_signature_constructed (GObject *object) { ESourceMailSignaturePrivate *priv; ESourceExtension *extension; ESource *source; const gchar *config_dir; const gchar *uid; gchar *base_dir; gchar *path; priv = E_SOURCE_MAIL_SIGNATURE (object)->priv; /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_source_mail_signature_parent_class)->constructed (object); extension = E_SOURCE_EXTENSION (object); source = e_source_extension_ref_source (extension); uid = e_source_get_uid (source); config_dir = e_get_user_config_dir (); base_dir = g_build_filename (config_dir, "signatures", NULL); path = g_build_filename (base_dir, uid, NULL); priv->file = g_file_new_for_path (path); g_mkdir_with_parents (base_dir, 0700); g_free (base_dir); g_free (path); g_object_unref (source); } static void e_source_mail_signature_class_init (ESourceMailSignatureClass *class) { GObjectClass *object_class; ESourceExtensionClass *extension_class; object_class = G_OBJECT_CLASS (class); object_class->set_property = source_mail_signature_set_property; object_class->get_property = source_mail_signature_get_property; object_class->dispose = source_mail_signature_dispose; object_class->finalize = source_mail_signature_finalize; object_class->constructed = source_mail_signature_constructed; extension_class = E_SOURCE_EXTENSION_CLASS (class); extension_class->name = E_SOURCE_EXTENSION_MAIL_SIGNATURE; g_object_class_install_property ( object_class, PROP_FILE, g_param_spec_object ( "file", "File", "File containing signature content", G_TYPE_FILE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_MIME_TYPE, g_param_spec_string ( "mime-type", "MIME Type", "MIME type of the signature content", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS | E_SOURCE_PARAM_SETTING)); } static void e_source_mail_signature_init (ESourceMailSignature *extension) { extension->priv = e_source_mail_signature_get_instance_private (extension); } /** * e_source_mail_signature_get_file: * @extension: an #ESourceMailSignature * * Returns a #GFile instance pointing to the signature file for @extension. * The signature file may be a regular file containing the static signature * content, or it may be a symbolic link to an executable file that produces * the signature content. * * e_source_mail_signature_load() uses this to load the signature content. * * Returns: (transfer none): a #GFile * * Since: 3.6 **/ GFile * e_source_mail_signature_get_file (ESourceMailSignature *extension) { g_return_val_if_fail (E_IS_SOURCE_MAIL_SIGNATURE (extension), NULL); return extension->priv->file; } /** * e_source_mail_signature_get_mime_type: * @extension: an #ESourceMailSignature * * Returns the MIME type of the signature content for @extension, or %NULL * if it has not yet been determined. * * e_source_mail_signature_load() sets this automatically if the MIME type * has not yet been determined. * * Returns: (nullable): the MIME type of the signature content, or %NULL * * Since: 3.6 **/ const gchar * e_source_mail_signature_get_mime_type (ESourceMailSignature *extension) { g_return_val_if_fail (E_IS_SOURCE_MAIL_SIGNATURE (extension), NULL); return extension->priv->mime_type; } /** * e_source_mail_signature_dup_mime_type: * @extension: an #ESourceMailSignature * * Thread-safe variation of e_source_mail_signature_get_mime_type(). * Use this function when accessing @extension from multiple threads. * * The returned string should be freed with g_free() when no longer needed. * * Returns: (nullable): a newly-allocated copy of #ESourceMailSignature:mime-type, * or %NULL * * Since: 3.6 **/ gchar * e_source_mail_signature_dup_mime_type (ESourceMailSignature *extension) { const gchar *protected; gchar *duplicate; g_return_val_if_fail (E_IS_SOURCE_MAIL_SIGNATURE (extension), NULL); e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); protected = e_source_mail_signature_get_mime_type (extension); duplicate = g_strdup (protected); e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); return duplicate; } /** * e_source_mail_signature_set_mime_type: * @extension: an #ESourceMailSignature * @mime_type: (nullable): a MIME type, or %NULL * * Sets the MIME type of the signature content for @extension. * * e_source_mail_signature_load() sets this automatically if the MIME type * has not yet been determined. * * The internal copy of @mime_type is automatically stripped of leading * and trailing whitespace. If the resulting string is empty, %NULL is * set instead. * * Since: 3.6 **/ void e_source_mail_signature_set_mime_type (ESourceMailSignature *extension, const gchar *mime_type) { g_return_if_fail (E_IS_SOURCE_MAIL_SIGNATURE (extension)); e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); if (e_util_strcmp0 (extension->priv->mime_type, mime_type) == 0) { e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); return; } g_free (extension->priv->mime_type); extension->priv->mime_type = e_util_strdup_strip (mime_type); e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); g_object_notify (G_OBJECT (extension), "mime-type"); } /********************** e_source_mail_signature_load() ***********************/ /* Helper for e_source_mail_signature_load() */ static void source_mail_signature_load_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { AsyncContext *async_context; GError *error = NULL; async_context = g_simple_async_result_get_op_res_gpointer (simple); e_source_mail_signature_load_sync ( E_SOURCE (object), &async_context->contents, &async_context->length, cancellable, &error); if (error != NULL) g_simple_async_result_take_error (simple, error); } /** * e_source_mail_signature_load_sync: * @source: an #ESource * @contents: (out): return location for the signature content * @length: (optional) (out): return location for the length of the signature * content, or %NULL if the length is not needed * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Loads a signature from the signature file for @source, which is * given by e_source_mail_signature_get_file(). The signature contents * are placed in @contents, and @length is set to the size of the @contents * string. The @contents string should be freed with g_free() when no * longer needed. * * If the signature file is executable, it will be executed and its output * captured as the email signature content. If the signature file is not * executable, the email signature content is read directly from the file. * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_mail_signature_load_sync (ESource *source, gchar **contents, gsize *length, GCancellable *cancellable, GError **error) { ESourceMailSignature *extension; GFileInfo *file_info; GFile *file; const gchar *content_type; const gchar *extension_name; gchar *local_contents = NULL; gboolean can_execute; gboolean success; gchar *guessed_content_type; gchar *command_line; gchar *mime_type; gchar *path; g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_return_val_if_fail (contents != NULL, FALSE); extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE; extension = e_source_get_extension (source, extension_name); file = e_source_mail_signature_get_file (extension); file_info = g_file_query_info ( file, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, G_FILE_QUERY_INFO_NONE, cancellable, error); if (file_info == NULL) return FALSE; can_execute = g_file_info_get_attribute_boolean ( file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE); content_type = g_file_info_get_content_type (file_info); mime_type = g_content_type_get_mime_type (content_type); if (can_execute) goto execute; /*** Load signature file contents ***/ success = g_file_load_contents ( file, cancellable, &local_contents, NULL, NULL, error); if (!success) goto exit; g_return_val_if_fail (local_contents != NULL, FALSE); /* Signatures are saved as UTF-8, but we still need to check that * the signature is valid UTF-8 because the user may be opening a * signature file this is in his/her locale character set. If it * is not UTF-8 then try converting from the current locale. */ if (!g_utf8_validate (local_contents, -1, NULL)) { gchar *utf8; utf8 = g_locale_to_utf8 ( local_contents, -1, NULL, NULL, error); if (utf8 == NULL) { success = FALSE; goto exit; } g_free (local_contents); local_contents = utf8; } goto exit; execute: /*** Execute signature file and capture output ***/ path = g_file_get_path (file); if (path == NULL) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Signature script must be a local file")); success = FALSE; goto exit; } /* Enclose the path in single-quotes for compatibility on Windows. * (See g_spawn_command_line_sync() documentation for rationale.) */ command_line = g_strdup_printf ("'%s'", path); success = g_spawn_command_line_sync ( command_line, &local_contents, NULL, NULL, error); g_free (command_line); g_free (path); /* Check if we failed to spawn the script. */ if (!success) goto exit; /* Check if we were cancelled while the script was running. */ if (g_cancellable_set_error_if_cancelled (cancellable, error)) { success = FALSE; goto exit; } g_return_val_if_fail (local_contents != NULL, FALSE); /* Signature scripts are supposed to generate UTF-8 content, but * because users are known to never read the manual, we try to do * our best if the content isn't valid UTF-8 by assuming that the * content is in the user's locale character set. */ if (!g_utf8_validate (local_contents, -1, NULL)) { gchar *utf8; utf8 = g_locale_to_utf8 ( local_contents, -1, NULL, NULL, error); if (utf8 == NULL) { success = FALSE; goto exit; } g_free (local_contents); local_contents = utf8; } g_free (mime_type); /* Try and guess the content type of the script output * so it can be applied correctly to the mail message. */ guessed_content_type = g_content_type_guess ( NULL, (guchar *) local_contents, strlen (local_contents), NULL); mime_type = g_content_type_get_mime_type (guessed_content_type); g_free (guessed_content_type); exit: if (success) { const gchar *ext_mime_type; if (length != NULL) *length = strlen (local_contents); *contents = local_contents; local_contents = NULL; ext_mime_type = e_source_mail_signature_get_mime_type (extension); /* Don't override the MIME type if it's already set. */ if (ext_mime_type == NULL || *ext_mime_type == '\0') e_source_mail_signature_set_mime_type ( extension, mime_type); } g_object_unref (file_info); g_free (local_contents); g_free (mime_type); return success; } /** * e_source_mail_signature_load: * @source: an #ESource * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchronously loads a signature from the signature file for @source, * which is given by e_source_mail_signature_get_file(). * * If the signature file is executable, it will be executed and its output * captured as the email signature content. If the signature file is not * executable, the email signature content is read directly from the file. * * When the operation is finished, @callback will be called. You can * then call e_source_mail_signature_load_finish() to get the result of * the operation. * * Since: 3.6 **/ void e_source_mail_signature_load (ESource *source, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_if_fail (E_IS_SOURCE (source)); async_context = g_slice_new0 (AsyncContext); simple = g_simple_async_result_new ( G_OBJECT (source), callback, user_data, e_source_mail_signature_load); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_set_op_res_gpointer ( simple, async_context, (GDestroyNotify) async_context_free); g_simple_async_result_run_in_thread ( simple, source_mail_signature_load_thread, io_priority, cancellable); g_object_unref (simple); } /** * e_source_mail_signature_load_finish: * @source: an #ESource * @result: a #GAsyncResult * @contents: (out): return location for the signature content * @length: (optional) (out): return location for the length of the signature * content, or %NULL if the length is not needed * @error: return location for a #GError, or %NULL * * Finishes an operation started with e_source_mail_signature_load(). The * signature file contents are placed in @contents, and @length is set to * the size of the @contents string. The @contents string should be freed * with g_free() when no longer needed. * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_mail_signature_load_finish (ESource *source, GAsyncResult *result, gchar **contents, gsize *length, GError **error) { GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (source), e_source_mail_signature_load), FALSE); g_return_val_if_fail (contents != NULL, FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); async_context = g_simple_async_result_get_op_res_gpointer (simple); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; g_return_val_if_fail (async_context->contents != NULL, FALSE); *contents = async_context->contents; async_context->contents = NULL; if (length != NULL) *length = async_context->length; return TRUE; } /********************* e_source_mail_signature_replace() *********************/ /* Helper for e_source_mail_signature_replace() */ static void source_mail_signature_replace_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { AsyncContext *async_context; GError *error = NULL; async_context = g_simple_async_result_get_op_res_gpointer (simple); e_source_mail_signature_replace_sync ( E_SOURCE (object), async_context->contents, async_context->length, cancellable, &error); if (error != NULL) g_simple_async_result_take_error (simple, error); } /** * e_source_mail_signature_replace_sync: * @source: an #ESource * @contents: the signature contents * @length: the length of @contents in bytes * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Replaces the signature file for @source with the given @contents * of @length bytes. The signature file for @source is given by * e_source_mail_signature_get_file(). * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_mail_signature_replace_sync (ESource *source, const gchar *contents, gsize length, GCancellable *cancellable, GError **error) { ESourceMailSignature *extension; const gchar *extension_name; GFile *file; g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_return_val_if_fail (contents != NULL, FALSE); extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE; extension = e_source_get_extension (source, extension_name); file = e_source_mail_signature_get_file (extension); return g_file_replace_contents ( file, contents, length, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error); } /** * e_source_mail_signature_replace: * @source: an #ESource * @contents: the signature contents * @length: the length of @contents in bytes * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchrously replaces the signature file for @source with the given * @contents of @length bytes. The signature file for @source is given * by e_source_mail_signature_get_file(). * * When the operation is finished, @callback will be called. You can * then call e_source_mail_signature_replace_finish() to get the result * of the operation. * * Since: 3.6 **/ void e_source_mail_signature_replace (ESource *source, const gchar *contents, gsize length, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (contents != NULL); async_context = g_slice_new0 (AsyncContext); async_context->contents = g_strdup (contents); async_context->length = length; simple = g_simple_async_result_new ( G_OBJECT (source), callback, user_data, e_source_mail_signature_replace); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_set_op_res_gpointer ( simple, async_context, (GDestroyNotify) async_context_free); g_simple_async_result_run_in_thread ( simple, source_mail_signature_replace_thread, io_priority, cancellable); g_object_unref (simple); } /** * e_source_mail_signature_replace_finish: * @source: an #ESource * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes an operation started with e_source_mail_signature_replace(). * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_mail_signature_replace_finish (ESource *source, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (source), e_source_mail_signature_replace), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); /* Assume success unless a GError is set. */ return !g_simple_async_result_propagate_error (simple, error); } /********************* e_source_mail_signature_symlink() *********************/ /* Helper for e_source_mail_signature_symlink() */ static void source_mail_signature_symlink_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { AsyncContext *async_context; GError *error = NULL; async_context = g_simple_async_result_get_op_res_gpointer (simple); e_source_mail_signature_symlink_sync ( E_SOURCE (object), async_context->symlink_target, cancellable, &error); if (error != NULL) g_simple_async_result_take_error (simple, error); } /** * e_source_mail_signature_symlink_sync: * @source: an #ESource * @symlink_target: executable filename to link to * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Replaces the signature file for @source with a symbolic link to * @symlink_target, which should be an executable file that prints * a mail signature to standard output. The signature file for * @source is given by e_source_mail_signature_get_file(). * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_mail_signature_symlink_sync (ESource *source, const gchar *symlink_target, GCancellable *cancellable, GError **error) { ESourceMailSignature *extension; const gchar *extension_name; GFile *file; g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_return_val_if_fail (symlink_target != NULL, FALSE); extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE; extension = e_source_get_extension (source, extension_name); file = e_source_mail_signature_get_file (extension); /* The file may not exist, so we don't care if this fails. * If it fails for a different reason than G_IO_ERROR_NOT_FOUND * then the next step will probably also fail and we'll capture * THAT error. */ g_file_delete (file, cancellable, NULL); return g_file_make_symbolic_link ( file, symlink_target, cancellable, error); } /** * e_source_mail_signature_symlink: * @source: an #ESource * @symlink_target: executable filename to link to * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchronously replaces the signature file for @source with a symbolic * link to @symlink_target, which should be an executable file that prints * a mail signature to standard output. The signature file for @source * is given by e_source_mail_signature_get_file(). * * When the operation is finished, @callback will be called. You can * then call e_source_mail_signature_symlink_finish() to get the result * of the operation. * * Since: 3.6 **/ void e_source_mail_signature_symlink (ESource *source, const gchar *symlink_target, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (symlink_target != NULL); async_context = g_slice_new0 (AsyncContext); async_context->symlink_target = g_strdup (symlink_target); simple = g_simple_async_result_new ( G_OBJECT (source), callback, user_data, e_source_mail_signature_symlink); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_set_op_res_gpointer ( simple, async_context, (GDestroyNotify) async_context_free); g_simple_async_result_run_in_thread ( simple, source_mail_signature_symlink_thread, io_priority, cancellable); g_object_unref (simple); } /** * e_source_mail_signature_symlink_finish: * @source: an #ESource * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes an operation started with e_source_mail_signature_symlink(). * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_mail_signature_symlink_finish (ESource *source, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (source), e_source_mail_signature_symlink), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); /* Assume success unless a GError is set. */ return !g_simple_async_result_propagate_error (simple, error); }