1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2009-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 "address_keeper.h"
29 #include "address_keeper_prefs.h"
30 #include "addr_compl.h"
31 #include "addrbook.h"
32 #include "codeconv.h"
33 #include "prefs_common.h"
34 
35 /** Identifier for the hook. */
36 static gulong hook_id = HOOK_NONE;
37 
38 /**
39  * Extracts name from an address.
40  *
41  * @param addr The full address.
42  * @return The name found in the address as a newly allocated string, or NULL if
43  * not found.
44  */
get_name_from_addr(const gchar * addr)45 gchar *get_name_from_addr(const gchar *addr)
46 {
47 	gchar *name = NULL;
48 
49 	if (addr == NULL || *addr == '\0')
50 		return NULL;
51 	name = strchr(addr, '@');
52 	if (name == NULL)
53 		return NULL;
54 	--name;
55 	while (name >= addr && !g_ascii_isspace(*name)) --name;
56 	while (name >= addr && g_ascii_isspace(*name)) --name;
57 	if (name > addr) {
58 		++name; /* recover non-space char */
59 		return g_strndup(addr, name - addr);
60 	}
61 	return NULL;
62 }
63 
64 /**
65  * Extracts comment from an address.
66  *
67  * @param addr The full address.
68  * @return The comment found in the address as a newly allocated string, or NULL if
69  * not found.
70  */
get_comment_from_addr(const gchar * addr)71 gchar *get_comment_from_addr(const gchar *addr)
72 {
73 	gchar *comm = NULL;
74 
75 	if (addr == NULL || *addr == '\0')
76 		return NULL;
77 	comm = strchr(addr, '@');
78 	if (comm == NULL)
79 		return NULL;
80 	++comm;
81 	while (*comm && !g_ascii_isspace(*comm)) ++comm;
82 	while (*comm && g_ascii_isspace(*comm)) ++comm;
83 	if (*comm)
84 		return g_strdup(comm);
85 	return NULL;
86 }
87 
88 /**
89  * Checks an address for matching a blocked address pattern.
90  *
91  * @param addr The full address.
92  * @param blocked The regexp matching blocked addresses.
93  *
94  * @return TRUE if given address matches any of the patterns, FALSE otherwise.
95  */
matches_blocked_address(gchar * addr,MatcherList * blocked)96 gboolean matches_blocked_address(gchar *addr, MatcherList *blocked)
97 {
98 	if (blocked != NULL) {
99 		MsgInfo info;
100 
101 		info.subject = addr;
102 		return matcherlist_match(blocked, &info);
103 	}
104 	return FALSE;
105 }
106 
107 /**
108  * Saves an address to the configured addressbook folder if not known.
109  *
110  * @param abf The address book file containing target folder.
111  * @param folder The address book folder where addresses are added.
112  * @param addr The address to be added.
113  * @param blocked The regexp matching blocked addresses.
114  */
keep_if_unknown(AddressBookFile * abf,ItemFolder * folder,gchar * addr,MatcherList * blocked)115 void keep_if_unknown(AddressBookFile * abf, ItemFolder * folder, gchar *addr, MatcherList *blocked)
116 {
117 	gchar *clean_addr = NULL;
118 	gchar *keepto = addkeeperprefs.addressbook_folder;
119 
120 	debug_print("checking addr '%s'\n", addr);
121 	if (matches_blocked_address(addr, blocked)) {
122 		debug_print("addr '%s' is blocked by regexp\n", addr);
123 		return;
124 	}
125 	clean_addr = g_strdup(addr);
126 	extract_address(clean_addr);
127 	start_address_completion(NULL);
128 	if (complete_matches_found(clean_addr) == 0) {
129 		gchar *a_name;
130 		gchar *a_comment;
131 		debug_print("adding addr '%s' to addressbook '%s'\n",
132 			    clean_addr, keepto);
133 		a_name = get_name_from_addr(addr);
134 		a_comment = get_comment_from_addr(addr);
135 		if (!addrbook_add_contact(abf, folder, a_name, clean_addr, a_comment)) {
136 			g_warning("contact could not be added");
137 		} else {
138 			addressbook_refresh();
139 		}
140 		if (a_name != NULL)
141 			g_free(a_name);
142 		if (a_comment != NULL)
143 			g_free(a_comment);
144 	} else {
145 		debug_print("found addr '%s' in addressbook '%s', skipping\n",
146 			    clean_addr, keepto);
147 	}
148 	end_address_completion();
149 	g_free(clean_addr);
150 }
151 
152 /**
153  * Callback function to be called before sending the mail.
154  *
155  * @param source The composer to be checked.
156  * @param data Additional data.
157  *
158  * @return FALSE always: we're only annotating addresses.
159  */
addrk_before_send_hook(gpointer source,gpointer data)160 static gboolean addrk_before_send_hook(gpointer source, gpointer data)
161 {
162 	Compose *compose = (Compose *)source;
163 	AddressDataSource *book = NULL;
164 	AddressBookFile *abf = NULL;
165 	ItemFolder *folder = NULL;
166 	gchar *keepto = addkeeperprefs.addressbook_folder;
167 	GSList *cur;
168 	const gchar *to_hdr;
169 	const gchar *cc_hdr;
170 	const gchar *bcc_hdr;
171 	MatcherList *blocked = NULL;
172 
173 	debug_print("address_keeper invoked!\n");
174 	if (compose->batch)
175 		return FALSE;	/* do not check while queuing */
176 
177 	if (keepto == NULL || *keepto == '\0') {
178 		g_warning("addressbook folder not configured");
179 		return FALSE;
180 	}
181 
182 	if (!addressbook_peek_folder_exists(keepto, &book, &folder)) {
183 		g_warning("addressbook folder not found '%s'", keepto);
184 		return FALSE;
185 	}
186 	if (!book) {
187 		g_warning("addressbook_peek_folder_exists: NULL book");
188 		return FALSE;
189 	}
190 	abf = book->rawDataSource;
191 
192 	to_hdr = prefs_common_translated_header_name("To:");
193 	cc_hdr = prefs_common_translated_header_name("Cc:");
194 	bcc_hdr = prefs_common_translated_header_name("Bcc:");
195 
196 	if (addkeeperprefs.block_matching_addrs != NULL
197 			&& addkeeperprefs.block_matching_addrs[0] != '\0') {
198 		blocked = matcherlist_new_from_lines(addkeeperprefs.block_matching_addrs, FALSE, FALSE);
199 		if (blocked == NULL)
200 			g_warning("couldn't allocate matcher");
201 	}
202 	for (cur = compose->header_list; cur != NULL; cur = cur->next) {
203 		gchar *header;
204 		gchar *entry;
205 		header = gtk_editable_get_chars(GTK_EDITABLE(
206 				gtk_bin_get_child(GTK_BIN(
207 					(((ComposeHeaderEntry *)cur->data)->combo)))), 0, -1);
208 		entry = gtk_editable_get_chars(GTK_EDITABLE(
209 				((ComposeHeaderEntry *)cur->data)->entry), 0, -1);
210 		g_strstrip(entry);
211 		g_strstrip(header);
212 		if (*entry != '\0') {
213 			if (!g_ascii_strcasecmp(header, to_hdr)
214 				&& addkeeperprefs.keep_to_addrs == TRUE) {
215 				keep_if_unknown(abf, folder, entry, blocked);
216 			}
217 			if (!g_ascii_strcasecmp(header, cc_hdr)
218 				&& addkeeperprefs.keep_cc_addrs == TRUE) {
219 				keep_if_unknown(abf, folder, entry, blocked);
220 			}
221 			if (!g_ascii_strcasecmp(header, bcc_hdr)
222 				&& addkeeperprefs.keep_bcc_addrs == TRUE) {
223 				keep_if_unknown(abf, folder, entry, blocked);
224 			}
225 		}
226 		g_free(header);
227 		g_free(entry);
228 	}
229 	if (blocked != NULL)
230 		matcherlist_free(blocked);
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, PLUGIN_NAME, error))
246 		return -1;
247 
248 	hook_id = hooks_register_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST,
249 				      addrk_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 	address_keeper_prefs_init();
257 
258 	debug_print("Address Keeper 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 	address_keeper_prefs_done();
271 	debug_print("Address Keeper 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 PLUGIN_NAME;
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 _("Keeps all recipient addresses in an addressbook folder.");
293 }
294 
295 /**
296  * Get the kind of plugin.
297  *
298  * @return The "GTK2" constant.
299  */
plugin_type(void)300 const gchar *plugin_type(void)
301 {
302 	return "GTK2";
303 }
304 
305 /**
306  * Get the license acronym the plugin is released under.
307  *
308  * @return The "GPL3+" constant.
309  */
plugin_licence(void)310 const gchar *plugin_licence(void)
311 {
312 	return "GPL3+";
313 }
314 
315 /**
316  * Get the version of the plugin.
317  *
318  * @return The current version string.
319  */
plugin_version(void)320 const gchar *plugin_version(void)
321 {
322 	return VERSION;
323 }
324 
325 /**
326  * Get the features implemented by the plugin.
327  *
328  * @return A constant PluginFeature structure with the features.
329  */
plugin_provides(void)330 struct PluginFeature *plugin_provides(void)
331 {
332 	static struct PluginFeature features[] =
333 		{ {PLUGIN_OTHER, N_("Address Keeper")},
334 		  {PLUGIN_NOTHING, NULL}};
335 
336 	return features;
337 }
338 
339