Fixed hook_id declarations to be gulong instead of guint.
[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) 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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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