2013-03-15 [colin] 3.9.0cvs127
[claws.git] / src / plugins / attachwarner / attachwarner.c
1 /*
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
5  *
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.
10  *
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.
15  *
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.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #include "claws-features.h"
24 #endif
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28
29 #include "version.h"
30 #include "attachwarner.h"
31 #include "attachwarner_prefs.h"
32 #include "codeconv.h"
33 #include "prefs_common.h"
34
35 /** Identifier for the hook. */
36 static guint hook_id;
37
38 #ifdef G_OS_UNIX
39 /**
40  * Builds a single regular expresion from an array of srings.
41  *
42  * @param strings The lines containing the different sub-regexp.
43  *
44  * @return The newly allocated regexp.
45  */
46 static gchar *build_complete_regexp(gchar **strings)
47 {
48         int i = 0;
49         gchar *expr = NULL;
50         while (strings && strings[i] && *strings[i]) {
51                 int old_len = expr ? strlen(expr):0;
52                 int new_len = 0;
53                 gchar *tmpstr = NULL;
54
55                 if (g_utf8_validate(strings[i], -1, NULL))
56                         tmpstr = g_strdup(strings[i]);
57                 else
58                         tmpstr = conv_codeset_strdup(strings[i], 
59                                         conv_get_locale_charset_str_no_utf8(),
60                                         CS_INTERNAL);
61
62                 if (strstr(tmpstr, "\n"))
63                         *(strstr(tmpstr, "\n")) = '\0';
64
65                 new_len = strlen(tmpstr);
66
67                 expr = g_realloc(expr, 
68                         expr ? (old_len + strlen("|()") + new_len + 1)
69                              : (strlen("()") + new_len + 1));
70                 
71                 if (old_len) {
72                         strcpy(expr + old_len, "|(");
73                         strcpy(expr + old_len + 2, tmpstr);
74                         strcpy(expr + old_len + 2 + new_len, ")");
75                 } else {
76                         strcpy(expr+old_len, "(");
77                         strcpy(expr+old_len + 1, tmpstr);
78                         strcpy(expr+old_len + 1 + new_len, ")");
79                 }
80                 g_free(tmpstr);
81                 i++;
82         }
83         return expr;
84 }
85 #endif
86 /**
87  * Creates the matcher.
88  *
89  * @return A newly allocated regexp matcher or null if no memory is available.
90  */
91 MatcherList * new_matcherlist(void)
92 {
93         MatcherProp *m = NULL;
94         GSList *matchers = NULL;
95         gchar **strings = g_strsplit(attwarnerprefs.match_strings, "\n", -1);
96
97 #ifdef G_OS_UNIX
98         gchar *expr = NULL;
99         expr = build_complete_regexp(strings);
100         debug_print("building matcherprop for expr '%s'\n", expr?expr:"NULL");
101         
102         m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL, MATCHTYPE_REGEXP, 
103                             expr, 0);
104         if (m == NULL) {
105                 /* print error message */
106                 debug_print("failed to allocate memory for matcherprop\n");
107         } else {
108                 matchers = g_slist_append(matchers, m);
109         }
110
111         g_free(expr);
112 #else
113         int i = 0;
114         while (strings && strings[i] && *strings[i]) {
115                 m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL, MATCHTYPE_MATCHCASE, 
116                             strings[i], 0);
117                 if (m == NULL) {
118                         /* print error message */
119                         debug_print("failed to allocate memory for matcherprop\n");
120                 } else {
121                         matchers = g_slist_append(matchers, m);
122                 }
123                 i++;
124         }
125 #endif
126         g_strfreev(strings);
127
128         return matcherlist_new(matchers, FALSE);
129 }
130
131 static AttachWarnerMention *aw_matcherlist_string_match(MatcherList *matchers, gchar *str, gchar *sig_separator)
132 {
133         MsgInfo info;
134         int i = 0;
135         gboolean ret = FALSE;
136         gchar **lines = NULL;
137         AttachWarnerMention *awm = NULL; 
138
139         if (str == NULL || *str == '\0') {
140                 return awm;
141         }
142         
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);
153                                 break;
154                         }
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);
161                         }
162                 }
163         } else {
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);
171                                 break;
172                         }
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);
177                 }
178         }
179         if (ret != FALSE) {
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);
184         }
185         g_strfreev(lines);
186
187         return awm;
188 }
189
190 /**
191  * Looks for attachment references in the composer text.
192  *
193  * @param compose The composer object to inspect.
194  *
195  * @return A pointer to an AttachWarnerMention if attachment references
196  * are found, or NULL otherwise.
197  */
198 AttachWarnerMention *are_attachments_mentioned(Compose *compose)
199 {
200         GtkTextView *textview = NULL;
201         GtkTextBuffer *textbuffer = NULL;
202         GtkTextIter start, end;
203         gchar *text = NULL;
204         AttachWarnerMention *mention = NULL;
205         MatcherList *matchers = NULL;
206
207         matchers = new_matcherlist();
208
209         if (matchers == NULL) {
210                 g_warning("couldn't allocate matcher");
211                 return FALSE;
212         }
213
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);
219
220         debug_print("checking text for attachment mentions\n");
221         if (text != NULL) {
222                 mention = aw_matcherlist_string_match(matchers, text, compose->account->sig_sep);
223                 g_free(text);
224         }       
225         if (matchers != NULL)
226                 matcherlist_free(matchers);
227         debug_print("done\n");
228         return mention;
229 }
230
231 /**
232  * Looks for files attached in the composer.
233  *
234  * @param compose The composer object to inspect.
235  *
236  * @return TRUE if there is one or more files attached, FALSE otherwise.
237  */
238 gboolean does_not_have_attachments(Compose *compose)
239 {
240         GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
241         GtkTreeModel *model;
242         GtkTreeIter iter;
243
244         model = gtk_tree_view_get_model(tree_view);
245
246         debug_print("checking for attachments existence\n");
247         if (!gtk_tree_model_get_iter_first(model, &iter))
248                 return TRUE;
249
250         return FALSE;
251 }
252
253 /**
254  * Check whether not check while redirecting or forwarding.
255  *
256  * @param mode The current compose->mode.
257  *
258  * @return TRUE for cancel further checking because it's being redirected or
259  *         forwarded and user configured not to check, FALSE otherwise.
260  */
261 gboolean do_not_check_redirect_forward(int mode)
262 {
263         switch (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)
269                         return TRUE;
270         default:
271                 return FALSE;
272         }
273 }
274
275 /**
276  * Callback function to be called before sending the mail.
277  * 
278  * @param source The composer to be checked.
279  * @param data Additional data.
280  *
281  * @return TRUE if no attachments are mentioned or files are attached,
282  *         FALSE if attachments are mentioned and no files are attached.
283  */
284 static gboolean attwarn_before_send_hook(gpointer source, gpointer data)
285 {
286         Compose *compose = (Compose *)source;
287         AttachWarnerMention *mention = NULL;
288
289         debug_print("attachwarner invoked\n");
290         if (compose->batch)
291                 return FALSE;   /* do not check while queuing */
292
293         if (do_not_check_redirect_forward(compose->mode))
294                 return FALSE;
295
296         mention = are_attachments_mentioned(compose); 
297         if (does_not_have_attachments(compose) && mention != NULL) { 
298                 AlertValue aval;
299                 gchar *button_label;
300                 gchar *message;
301                 
302                 debug_print("user has to decide\n");
303                 if (compose->sending)
304                         button_label = _("+_Send");
305                 else
306                         button_label = _("+_Queue");
307
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?"),
310                                 mention->line,
311                                 mention->context,
312                                 compose->sending?_("Send"):_("Queue"));
313                 aval = alertpanel(_("Attachment warning"), message,
314                                   GTK_STOCK_CANCEL, button_label, NULL);
315                 g_free(message);
316                 if (aval != G_ALERTALTERNATE)
317                         return TRUE;
318         }
319         if (mention != NULL) {
320                 if (mention->context != NULL)
321                         g_free(mention->context);
322                 g_free(mention);
323         }
324
325         return FALSE;   /* continue sending */
326 }
327
328 /**
329  * Initialize plugin.
330  *
331  * @param error  For storing the returned error message.
332  *
333  * @return 0 if initialization succeeds, -1 on failure.
334  */
335 gint plugin_init(gchar **error)
336 {
337         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
338                                   VERSION_NUMERIC, _("Attach warner"), error))
339                 return -1;
340
341         hook_id = hooks_register_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, 
342                                       attwarn_before_send_hook, NULL);
343         
344         if (hook_id == -1) {
345                 *error = g_strdup(_("Failed to register check before send hook"));
346                 return -1;
347         }
348
349         attachwarner_prefs_init();
350
351         debug_print("Attachment warner plugin loaded\n");
352
353         return 0;
354 }
355
356 /**
357  * Destructor for the plugin.
358  * Unregister the callback function and frees matcher.
359  */
360 gboolean plugin_done(void)
361 {       
362         hooks_unregister_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, hook_id);
363         attachwarner_prefs_done();
364         debug_print("Attachment warner plugin unloaded\n");
365         return TRUE;
366 }
367
368 /**
369  * Get the name of the plugin.
370  *
371  * @return The plugin name (maybe translated).
372  */
373 const gchar *plugin_name(void)
374 {
375         return _("Attach warner");
376 }
377
378 /**
379  * Get the description of the plugin.
380  *
381  * @return The plugin description (maybe translated).
382  */
383 const gchar *plugin_desc(void)
384 {
385         return _("Warns user if some reference to attachments is found in the "
386                  "message text and no file is attached.");
387 }
388
389 /**
390  * Get the kind of plugin.
391  *
392  * @return The "GTK2" constant.
393  */
394 const gchar *plugin_type(void)
395 {
396         return "GTK2";
397 }
398
399 /**
400  * Get the license acronym the plugin is released under.
401  *
402  * @return The "GPL" constant.
403  */
404 const gchar *plugin_licence(void)
405 {
406         return "GPL3+";
407 }
408
409 /**
410  * Get the version of the plugin.
411  *
412  * @return The current version string.
413  */
414 const gchar *plugin_version(void)
415 {
416         return VERSION;
417 }
418
419 /**
420  * Get the features implemented by the plugin.
421  *
422  * @return A constant PluginFeature structure with the features.
423  */
424 struct PluginFeature *plugin_provides(void)
425 {
426         static struct PluginFeature features[] = 
427                 { {PLUGIN_OTHER, N_("Attach warner")},
428                   {PLUGIN_NOTHING, NULL}};
429
430         return features;
431 }
432