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