1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2006-2015 Ricardo Mones and the Claws Mail Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26
27 #include "version.h"
28 #include "attachwarner.h"
29 #include "attachwarner_prefs.h"
30 #include "codeconv.h"
31 #include "prefs_common.h"
32
33 /** Identifier for the hook. */
34 static gulong hook_id = HOOK_NONE;
35
aw_matcherlist_string_match(MatcherList * matchers,gchar * str,gchar * sig_separator)36 static AttachWarnerMention *aw_matcherlist_string_match(MatcherList *matchers, gchar *str, gchar *sig_separator)
37 {
38 MsgInfo info;
39 int i = 0;
40 gboolean ret = FALSE;
41 gchar **lines = NULL;
42 AttachWarnerMention *awm = NULL;
43
44 if (str == NULL || *str == '\0') {
45 return awm;
46 }
47
48 lines = g_strsplit(str, "\n", -1);
49 if (attwarnerprefs.skip_quotes
50 && *prefs_common_get_prefs()->quote_chars != '\0') {
51 debug_print("checking without quotes\n");
52 for (i = 0; lines[i] != NULL && ret == FALSE; i++) {
53 if(attwarnerprefs.skip_signature
54 && sig_separator != NULL
55 && *sig_separator != '\0'
56 && strcmp(lines[i], sig_separator) == 0) {
57 debug_print("reached signature delimiter at line %d\n", i);
58 break;
59 }
60 if (line_has_quote_char(lines[i],
61 prefs_common_get_prefs()->quote_chars) == NULL) {
62 debug_print("testing line %d\n", i);
63 info.subject = lines[i];
64 ret = matcherlist_match(matchers, &info);
65 debug_print("line %d: %d\n", i, ret);
66 }
67 }
68 } else {
69 debug_print("checking with quotes\n");
70 for (i = 0; lines[i] != NULL && ret == FALSE; i++) {
71 if(attwarnerprefs.skip_signature
72 && sig_separator != NULL
73 && *sig_separator != '\0'
74 && strcmp(lines[i], sig_separator) == 0) {
75 debug_print("reached signature delimiter at line %d\n", i);
76 break;
77 }
78 debug_print("testing line %d\n", i);
79 info.subject = lines[i];
80 ret = matcherlist_match(matchers, &info);
81 debug_print("line %d: %d\n", i, ret);
82 }
83 }
84 if (ret != FALSE) {
85 awm = g_new0(AttachWarnerMention, 1);
86 awm->line = i; /* usual humans count lines from 1 */
87 awm->context = g_strdup(lines[i - 1]);
88 debug_print("found at line %d, context \"%s\"\n", awm->line, awm->context);
89 }
90 g_strfreev(lines);
91
92 return awm;
93 }
94
95 /**
96 * Looks for attachment references in the composer text.
97 *
98 * @param compose The composer object to inspect.
99 *
100 * @return A pointer to an AttachWarnerMention if attachment references
101 * are found, or NULL otherwise.
102 */
are_attachments_mentioned(Compose * compose)103 AttachWarnerMention *are_attachments_mentioned(Compose *compose)
104 {
105 GtkTextView *textview = NULL;
106 GtkTextBuffer *textbuffer = NULL;
107 GtkTextIter start, end;
108 gchar *text = NULL;
109 AttachWarnerMention *mention = NULL;
110 MatcherList *matchers = NULL;
111
112 matchers = matcherlist_new_from_lines(attwarnerprefs.match_strings, FALSE, attwarnerprefs.case_sensitive);
113
114 if (matchers == NULL) {
115 g_warning("couldn't allocate matcher");
116 return FALSE;
117 }
118
119 textview = GTK_TEXT_VIEW(compose->text);
120 textbuffer = gtk_text_view_get_buffer(textview);
121 gtk_text_buffer_get_start_iter(textbuffer, &start);
122 gtk_text_buffer_get_end_iter(textbuffer, &end);
123 text = gtk_text_buffer_get_text(textbuffer, &start, &end, FALSE);
124
125 debug_print("checking text for attachment mentions\n");
126 if (text != NULL) {
127 mention = aw_matcherlist_string_match(matchers, text, compose->account->sig_sep);
128 g_free(text);
129 }
130 if (matchers != NULL)
131 matcherlist_free(matchers);
132 debug_print("done\n");
133 return mention;
134 }
135
136 /**
137 * Looks for files attached in the composer.
138 *
139 * @param compose The composer object to inspect.
140 *
141 * @return TRUE if there is one or more files attached, FALSE otherwise.
142 */
does_not_have_attachments(Compose * compose)143 gboolean does_not_have_attachments(Compose *compose)
144 {
145 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
146 GtkTreeModel *model;
147 GtkTreeIter iter;
148
149 model = gtk_tree_view_get_model(tree_view);
150
151 debug_print("checking for attachments existence\n");
152 if (!gtk_tree_model_get_iter_first(model, &iter))
153 return TRUE;
154
155 return FALSE;
156 }
157
158 /**
159 * Check whether not check while redirecting or forwarding.
160 *
161 * @param mode The current compose->mode.
162 *
163 * @return TRUE for cancel further checking because it's being redirected or
164 * forwarded and user configured not to check, FALSE otherwise.
165 */
do_not_check_redirect_forward(int mode)166 gboolean do_not_check_redirect_forward(int mode)
167 {
168 switch (mode) {
169 case COMPOSE_FORWARD:
170 case COMPOSE_FORWARD_AS_ATTACH:
171 case COMPOSE_FORWARD_INLINE:
172 case COMPOSE_REDIRECT:
173 if (attwarnerprefs.skip_forwards_and_redirections)
174 return TRUE;
175 default:
176 return FALSE;
177 }
178 }
179
180 /**
181 * Callback function to be called before sending the mail.
182 *
183 * @param source The composer to be checked.
184 * @param data Additional data.
185 *
186 * @return TRUE if no attachments are mentioned or files are attached,
187 * FALSE if attachments are mentioned and no files are attached.
188 */
attwarn_before_send_hook(gpointer source,gpointer data)189 static gboolean attwarn_before_send_hook(gpointer source, gpointer data)
190 {
191 Compose *compose = (Compose *)source;
192 AttachWarnerMention *mention = NULL;
193
194 debug_print("attachwarner invoked\n");
195 if (compose->batch)
196 return FALSE; /* do not check while queuing */
197
198 if (do_not_check_redirect_forward(compose->mode))
199 return FALSE;
200
201 mention = are_attachments_mentioned(compose);
202 if (does_not_have_attachments(compose) && mention != NULL) {
203 AlertValue aval;
204 gchar *message;
205 gchar *bold_text;
206
207 bold_text = g_strdup_printf("<span weight=\"bold\">%.20s</span>...",
208 mention->context);
209 message = g_strdup_printf(
210 _("An attachment is mentioned in the mail you're sending, "
211 "but no file was attached. Mention appears on line %d, "
212 "which begins with text: %s\n\n%s"),
213 mention->line,
214 bold_text,
215 compose->sending?_("Send it anyway?"):_("Queue it anyway?"));
216 aval = alertpanel(_("Attachment warning"), message,
217 GTK_STOCK_CANCEL,
218 compose->sending ? _("_Send") : _("Queue"),
219 NULL,
220 ALERTFOCUS_SECOND);
221 g_free(message);
222 g_free(bold_text);
223 if (aval != G_ALERTALTERNATE)
224 return TRUE;
225 }
226 if (mention != NULL) {
227 if (mention->context != NULL)
228 g_free(mention->context);
229 g_free(mention);
230 }
231
232 return FALSE; /* continue sending */
233 }
234
235 /**
236 * Initialize plugin.
237 *
238 * @param error For storing the returned error message.
239 *
240 * @return 0 if initialization succeeds, -1 on failure.
241 */
plugin_init(gchar ** error)242 gint plugin_init(gchar **error)
243 {
244 if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
245 VERSION_NUMERIC, _("Attach warner"), error))
246 return -1;
247
248 hook_id = hooks_register_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST,
249 attwarn_before_send_hook, NULL);
250
251 if (hook_id == HOOK_NONE) {
252 *error = g_strdup(_("Failed to register check before send hook"));
253 return -1;
254 }
255
256 attachwarner_prefs_init();
257
258 debug_print("Attachment warner plugin loaded\n");
259
260 return 0;
261 }
262
263 /**
264 * Destructor for the plugin.
265 * Unregister the callback function and frees matcher.
266 */
plugin_done(void)267 gboolean plugin_done(void)
268 {
269 hooks_unregister_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, hook_id);
270 attachwarner_prefs_done();
271 debug_print("Attachment warner plugin unloaded\n");
272 return TRUE;
273 }
274
275 /**
276 * Get the name of the plugin.
277 *
278 * @return The plugin name (maybe translated).
279 */
plugin_name(void)280 const gchar *plugin_name(void)
281 {
282 return _("Attach warner");
283 }
284
285 /**
286 * Get the description of the plugin.
287 *
288 * @return The plugin description (maybe translated).
289 */
plugin_desc(void)290 const gchar *plugin_desc(void)
291 {
292 return _("Warns user if some reference to attachments is found in the "
293 "message text and no file is attached.");
294 }
295
296 /**
297 * Get the kind of plugin.
298 *
299 * @return The "GTK2" constant.
300 */
plugin_type(void)301 const gchar *plugin_type(void)
302 {
303 return "GTK2";
304 }
305
306 /**
307 * Get the license acronym the plugin is released under.
308 *
309 * @return The "GPL" constant.
310 */
plugin_licence(void)311 const gchar *plugin_licence(void)
312 {
313 return "GPL3+";
314 }
315
316 /**
317 * Get the version of the plugin.
318 *
319 * @return The current version string.
320 */
plugin_version(void)321 const gchar *plugin_version(void)
322 {
323 return VERSION;
324 }
325
326 /**
327 * Get the features implemented by the plugin.
328 *
329 * @return A constant PluginFeature structure with the features.
330 */
plugin_provides(void)331 struct PluginFeature *plugin_provides(void)
332 {
333 static struct PluginFeature features[] =
334 { {PLUGIN_OTHER, N_("Attach warner")},
335 {PLUGIN_NOTHING, NULL}};
336
337 return features;
338 }
339
340