2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail Team
4 * Copyright (C) 2006-2012 Ricardo Mones
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include "claws-features.h"
27 #include <glib/gi18n.h>
30 #include "attachwarner.h"
31 #include "attachwarner_prefs.h"
33 #include "prefs_common.h"
35 /** Identifier for the hook. */
40 * Builds a single regular expresion from an array of srings.
42 * @param strings The lines containing the different sub-regexp.
44 * @return The newly allocated regexp.
46 static gchar *build_complete_regexp(gchar **strings)
50 while (strings && strings[i] && *strings[i]) {
51 int old_len = expr ? strlen(expr):0;
55 if (g_utf8_validate(strings[i], -1, NULL))
56 tmpstr = g_strdup(strings[i]);
58 tmpstr = conv_codeset_strdup(strings[i],
59 conv_get_locale_charset_str_no_utf8(),
62 if (strstr(tmpstr, "\n"))
63 *(strstr(tmpstr, "\n")) = '\0';
65 new_len = strlen(tmpstr);
67 expr = g_realloc(expr,
68 expr ? (old_len + strlen("|()") + new_len + 1)
69 : (strlen("()") + new_len + 1));
72 strcpy(expr + old_len, "|(");
73 strcpy(expr + old_len + 2, tmpstr);
74 strcpy(expr + old_len + 2 + new_len, ")");
76 strcpy(expr+old_len, "(");
77 strcpy(expr+old_len + 1, tmpstr);
78 strcpy(expr+old_len + 1 + new_len, ")");
87 * Creates the matcher.
89 * @return A newly allocated regexp matcher or null if no memory is available.
91 MatcherList * new_matcherlist(void)
93 MatcherProp *m = NULL;
94 GSList *matchers = NULL;
95 gchar **strings = g_strsplit(attwarnerprefs.match_strings, "\n", -1);
99 expr = build_complete_regexp(strings);
100 debug_print("building matcherprop for expr '%s'\n", expr?expr:"NULL");
102 m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL, MATCHTYPE_REGEXP,
105 /* print error message */
106 debug_print("failed to allocate memory for matcherprop\n");
108 matchers = g_slist_append(matchers, m);
114 while (strings && strings[i] && *strings[i]) {
115 m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL, MATCHTYPE_MATCHCASE,
118 /* print error message */
119 debug_print("failed to allocate memory for matcherprop\n");
121 matchers = g_slist_append(matchers, m);
128 return matcherlist_new(matchers, FALSE);
131 static AttachWarnerMention *aw_matcherlist_string_match(MatcherList *matchers, gchar *str, gchar *sig_separator)
135 gboolean ret = FALSE;
136 gchar **lines = NULL;
137 AttachWarnerMention *awm = NULL;
139 if (str == NULL || *str == '\0') {
143 lines = g_strsplit(str, "\n", -1);
144 if (attwarnerprefs.skip_quotes
145 && *prefs_common_get_prefs()->quote_chars != '\0') {
146 debug_print("checking without quotes\n");
147 for (i = 0; lines[i] != NULL && ret == FALSE; i++) {
148 if(attwarnerprefs.skip_signature
149 && sig_separator != NULL
150 && *sig_separator != '\0'
151 && strcmp(lines[i], sig_separator) == 0) {
152 debug_print("reached signature delimiter at line %d\n", i);
155 if (line_has_quote_char(lines[i],
156 prefs_common_get_prefs()->quote_chars) == NULL) {
157 debug_print("testing line %d\n", i);
158 info.subject = lines[i];
159 ret = matcherlist_match(matchers, &info);
160 debug_print("line %d: %d\n", i, ret);
164 debug_print("checking with quotes\n");
165 for (i = 0; lines[i] != NULL && ret == FALSE; i++) {
166 if(attwarnerprefs.skip_signature
167 && sig_separator != NULL
168 && *sig_separator != '\0'
169 && strcmp(lines[i], sig_separator) == 0) {
170 debug_print("reached signature delimiter at line %d\n", i);
173 debug_print("testing line %d\n", i);
174 info.subject = lines[i];
175 ret = matcherlist_match(matchers, &info);
176 debug_print("line %d: %d\n", i, ret);
180 awm = g_new0(AttachWarnerMention, 1);
181 awm->line = i; /* usual humans count lines from 1 */
182 awm->context = g_strdup(lines[i - 1]);
183 debug_print("found at line %d, context \"%s\"\n", awm->line, awm->context);
191 * Looks for attachment references in the composer text.
193 * @param compose The composer object to inspect.
195 * @return A pointer to an AttachWarnerMention if attachment references
196 * are found, or NULL otherwise.
198 AttachWarnerMention *are_attachments_mentioned(Compose *compose)
200 GtkTextView *textview = NULL;
201 GtkTextBuffer *textbuffer = NULL;
202 GtkTextIter start, end;
204 AttachWarnerMention *mention = NULL;
205 MatcherList *matchers = NULL;
207 matchers = new_matcherlist();
209 if (matchers == NULL) {
210 g_warning("couldn't allocate matcher");
214 textview = GTK_TEXT_VIEW(compose->text);
215 textbuffer = gtk_text_view_get_buffer(textview);
216 gtk_text_buffer_get_start_iter(textbuffer, &start);
217 gtk_text_buffer_get_end_iter(textbuffer, &end);
218 text = gtk_text_buffer_get_text(textbuffer, &start, &end, FALSE);
220 debug_print("checking text for attachment mentions\n");
222 mention = aw_matcherlist_string_match(matchers, text, compose->account->sig_sep);
225 if (matchers != NULL)
226 matcherlist_free(matchers);
227 debug_print("done\n");
232 * Looks for files attached in the composer.
234 * @param compose The composer object to inspect.
236 * @return TRUE if there is one or more files attached, FALSE otherwise.
238 gboolean does_not_have_attachments(Compose *compose)
240 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
244 model = gtk_tree_view_get_model(tree_view);
246 debug_print("checking for attachments existence\n");
247 if (!gtk_tree_model_get_iter_first(model, &iter))
254 * Check whether not check while redirecting or forwarding.
256 * @param mode The current compose->mode.
258 * @return TRUE for cancel further checking because it's being redirected or
259 * forwarded and user configured not to check, FALSE otherwise.
261 gboolean do_not_check_redirect_forward(int mode)
264 case COMPOSE_FORWARD:
265 case COMPOSE_FORWARD_AS_ATTACH:
266 case COMPOSE_FORWARD_INLINE:
267 case COMPOSE_REDIRECT:
268 if (attwarnerprefs.skip_forwards_and_redirections)
276 * Callback function to be called before sending the mail.
278 * @param source The composer to be checked.
279 * @param data Additional data.
281 * @return TRUE if no attachments are mentioned or files are attached,
282 * FALSE if attachments are mentioned and no files are attached.
284 static gboolean attwarn_before_send_hook(gpointer source, gpointer data)
286 Compose *compose = (Compose *)source;
287 AttachWarnerMention *mention = NULL;
289 debug_print("attachwarner invoked\n");
291 return FALSE; /* do not check while queuing */
293 if (do_not_check_redirect_forward(compose->mode))
296 mention = are_attachments_mentioned(compose);
297 if (does_not_have_attachments(compose) && mention != NULL) {
302 debug_print("user has to decide\n");
303 if (compose->sending)
304 button_label = _("+_Send");
306 button_label = _("+_Queue");
308 message = g_strdup_printf(
309 _("An attachment is mentioned in the mail you're sending, but no file was attached. Mention appears on line %d, which begins with text: <span weight=\"bold\">%.20s</span>...\n\n%s it anyway?"),
312 compose->sending?_("Send"):_("Queue"));
313 aval = alertpanel(_("Attachment warning"), message,
314 GTK_STOCK_CANCEL, button_label, NULL);
316 if (aval != G_ALERTALTERNATE)
319 if (mention != NULL) {
320 if (mention->context != NULL)
321 g_free(mention->context);
325 return FALSE; /* continue sending */
331 * @param error For storing the returned error message.
333 * @return 0 if initialization succeeds, -1 on failure.
335 gint plugin_init(gchar **error)
337 if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
338 VERSION_NUMERIC, _("Attach warner"), error))
341 hook_id = hooks_register_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST,
342 attwarn_before_send_hook, NULL);
345 *error = g_strdup(_("Failed to register check before send hook"));
349 attachwarner_prefs_init();
351 debug_print("Attachment warner plugin loaded\n");
357 * Destructor for the plugin.
358 * Unregister the callback function and frees matcher.
360 gboolean plugin_done(void)
362 hooks_unregister_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, hook_id);
363 attachwarner_prefs_done();
364 debug_print("Attachment warner plugin unloaded\n");
369 * Get the name of the plugin.
371 * @return The plugin name (maybe translated).
373 const gchar *plugin_name(void)
375 return _("Attach warner");
379 * Get the description of the plugin.
381 * @return The plugin description (maybe translated).
383 const gchar *plugin_desc(void)
385 return _("Warns user if some reference to attachments is found in the "
386 "message text and no file is attached.");
390 * Get the kind of plugin.
392 * @return The "GTK2" constant.
394 const gchar *plugin_type(void)
400 * Get the license acronym the plugin is released under.
402 * @return The "GPL" constant.
404 const gchar *plugin_licence(void)
410 * Get the version of the plugin.
412 * @return The current version string.
414 const gchar *plugin_version(void)
420 * Get the features implemented by the plugin.
422 * @return A constant PluginFeature structure with the features.
424 struct PluginFeature *plugin_provides(void)
426 static struct PluginFeature features[] =
427 { {PLUGIN_OTHER, N_("Attach warner")},
428 {PLUGIN_NOTHING, NULL}};