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