fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / mutt.c
1 /*
2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3  * Copyright (C) 2001-2012 Match Grun 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
20 /*
21  * Functions necessary to access MUTT address book file.
22  */
23
24 #include "config.h"
25
26 #include <sys/stat.h>
27 #include <ctype.h>
28 #include <string.h>
29 #include <glib.h>
30
31 #include "utils.h"
32 #include "mgutils.h"
33 #include "mutt.h"
34 #include "addritem.h"
35 #include "addrcache.h"
36 #include "file-utils.h"
37
38 #define MUTT_HOME_FILE  ".muttrc"
39 #define MUTTBUFSIZE     2048
40 #define MUTT_TAG_ALIAS  "alias"
41
42 /*
43 * Create new object.
44 */
45 MuttFile *mutt_create() {
46         MuttFile *muttFile;
47         muttFile = g_new0( MuttFile, 1 );
48         muttFile->path = NULL;
49         muttFile->file = NULL;
50         muttFile->retVal = MGU_SUCCESS;
51         muttFile->uniqTable = g_hash_table_new( g_str_hash, g_str_equal );
52         muttFile->cbProgress = NULL;
53         return muttFile;
54 }
55
56 /*
57 * Properties...
58 */
59 void mutt_set_file( MuttFile* muttFile, const gchar *value ) {
60         cm_return_if_fail( muttFile != NULL );
61         muttFile->path = mgu_replace_string( muttFile->path, value );
62         g_strstrip( muttFile->path );
63 }
64
65 /*
66  * Free key in table.
67  */
68 static gint mutt_free_table_vis( gpointer key, gpointer value, gpointer data ) {
69         g_free( key );
70         return TRUE;
71 }
72
73 /*
74 * Free up object by releasing internal memory.
75 */
76 void mutt_free( MuttFile *muttFile ) {
77         cm_return_if_fail( muttFile != NULL );
78
79         /* Close file */
80         if( muttFile->file ) claws_fclose( muttFile->file );
81
82         /* Free internal stuff */
83         g_free( muttFile->path );
84
85         /* Free unique address table */
86         g_hash_table_foreach_remove( muttFile->uniqTable, mutt_free_table_vis, NULL );
87         g_hash_table_destroy( muttFile->uniqTable );
88
89         /* Clear pointers */
90         muttFile->file = NULL;
91         muttFile->path = NULL;
92         muttFile->retVal = MGU_SUCCESS;
93         muttFile->uniqTable = NULL;
94         muttFile->cbProgress = NULL;
95
96         /* Now release file object */
97         g_free( muttFile );
98 }
99
100 /*
101 * Open file for read.
102 * return: TRUE if file opened successfully.
103 */
104 static gint mutt_open_file( MuttFile* muttFile ) {
105         if( muttFile->path ) {
106                 muttFile->file = claws_fopen( muttFile->path, "rb" );
107                 if( ! muttFile->file ) {
108                         muttFile->retVal = MGU_OPEN_FILE;
109                         return muttFile->retVal;
110                 }
111         }
112         else {
113                 /* g_print( "file not specified\n" ); */
114                 muttFile->retVal = MGU_NO_FILE;
115                 return muttFile->retVal;
116         }
117
118         /* Setup a buffer area */
119         muttFile->retVal = MGU_SUCCESS;
120         return muttFile->retVal;
121 }
122
123 /*
124 * Close file.
125 */
126 static void mutt_close_file( MuttFile *muttFile ) {
127         cm_return_if_fail( muttFile != NULL );
128         if( muttFile->file ) claws_fclose( muttFile->file );
129         muttFile->file = NULL;
130 }
131
132 /*
133 * Read line of text from file.
134 * Enter: muttFile File object.
135 *        flagCont Continuation flag, set if back-slash character at EOL.
136 * Return: ptr to buffer where line starts.
137 */
138 static gchar *mutt_get_line( MuttFile *muttFile, gboolean *flagCont ) {
139         gchar buf[ MUTTBUFSIZE ];
140         int ch, lch;
141         int i = 0, li = 0;
142
143         *flagCont = FALSE;
144         if( claws_feof( muttFile->file ) ) 
145                 return NULL;
146
147         memset(buf, 0, MUTTBUFSIZE);
148
149         lch = '\0';
150         while( i < MUTTBUFSIZE-1 ) {
151                 ch = fgetc( muttFile->file );
152                 if( ch == '\0' || ch == EOF ) {
153                         if( i == 0 ) 
154                                 return NULL;
155                         break;
156                 }
157                 if( ch == '\n' ) {
158                         if( lch == '\\' ) {
159                                 /* Replace backslash with NULL */
160                                 if( li != 0 ) 
161                                         buf[li] = '\0';
162                                 *flagCont = TRUE;
163                         }
164                         break;
165                 }
166                 buf[i] = ch;
167                 li = i;
168                 lch = ch;
169                 i++;
170         }
171         buf[i]='\0';
172
173         /* Copy into private buffer */
174         return g_strdup( buf );
175 }
176
177 /*
178  * Parsed address data.
179  */
180 typedef struct _Mutt_ParsedRec_ Mutt_ParsedRec;
181 struct _Mutt_ParsedRec_ {
182         gchar *address;
183         gchar *name;
184 };
185
186 /*
187  * Free data record.
188  * Enter: rec Data record.
189  */
190 static void mutt_free_rec( Mutt_ParsedRec *rec ) {
191         if( rec ) {
192                 g_free( rec->address );
193                 g_free( rec->name );
194                 rec->address = NULL;
195                 rec->name = NULL;
196                 g_free( rec );
197         }
198 }
199
200 /*
201 * Parse recipient list for each address.
202 * Enter: rcpList   Recipients extracted from file.
203 *        addrCount Updated with recipient count.
204 * Return: Linked list of recipients.
205 */
206 static GSList *mutt_parse_rcplist( gchar *rcpList, gint *addrCount ) {
207         gchar *ptr, *pStart, *pEnd, *pAddr, *pName, *address, *name;
208         gchar ch;
209         GSList *list;
210         Mutt_ParsedRec *rec;
211         gint  cnt;
212
213         list = NULL;
214         cnt = 0;
215         pStart = rcpList;
216         while( pStart && *pStart ) {
217                 ptr = pStart;
218                 address = NULL;
219                 pName = pAddr = NULL;
220                 /* Chew up spaces */
221                 while( *ptr ) {
222                         if( ! isspace( *ptr ) ) break;
223                         ptr++;
224                 }
225
226                 /* Find address */
227                 while( *ptr ) {
228                         ch = *ptr;
229                         if( ch == '(' ) {
230                                 pAddr = pName = ptr;
231                                 break;
232                         }
233                         if( ch == ',' ) {
234                                 pAddr = ptr;
235                                 ptr++;
236                                 break;
237                         }
238                         if( isspace( ch ) ) {
239                                 pAddr = ptr;
240                                 break;
241                         }
242                         ptr++;
243                 }
244
245                 /* Extract address */
246                 if( pAddr ) {
247                         address = g_strndup( pStart, pAddr - pStart );
248                 }
249                 else {
250                         address = g_strdup( pStart );
251                 }
252                 g_strstrip( address );
253
254                 /* Chew up spaces */
255                 while( *ptr ) {
256                         ch = *ptr;
257                         if( ch == '(' ) {
258                                 pName = ptr;
259                                 break;
260                         }
261                         if( ch == ',' ) {
262                                 ptr++;
263                                 break;
264                         }
265                         ptr++;
266                         if( isspace( ch ) ) continue;
267                 }
268                 pStart = ptr;
269         
270                 /* Extract name (if any) */
271                 if( pName ) {
272                         /* Look for closing parens */
273                         pName++;
274                         pEnd = NULL;
275                         ptr = pName;
276                         while( *ptr ) {
277                                 if( *ptr == ')' ) {
278                                         pEnd = ptr;
279                                         break;
280                                 }
281                                 ptr++;
282                         }
283                         if( pEnd ) {
284                                 name = g_strndup( pName, pEnd - pName );
285                                 pEnd++;
286                                 if( *pEnd ) pEnd++;
287                         }
288                         else {
289                                 name = g_strdup( pName );
290                         }
291                         g_strstrip( name );
292                         pStart = pEnd;
293                 }
294                 else {
295                         name = g_strdup( "" );
296                 }
297
298                 /* New record */
299                 rec = g_new0( Mutt_ParsedRec, 1 );
300                 rec->address = address;
301                 rec->name = name;
302                 list = g_slist_append( list, rec );
303                 cnt++;
304
305                 /* mutt_print_rec( rec, stdout ); */
306         }
307         *addrCount = cnt;
308         return list;
309 }
310
311 /*
312  * Insert person and address into address cache.
313  * Enter: muttFile MUTT control data.
314  *        cache    Address cache.
315  *        address  E-Mail address.
316  *        name     Name.
317  * Return: E-Mail object, either inserted or found in hash table.
318  */
319 static ItemEMail *mutt_insert_table(
320                 MuttFile *muttFile, AddressCache *cache, gchar *address,
321                 gchar *name )
322 {
323         ItemPerson *person;
324         ItemEMail *email;
325         gchar *key;
326
327         /* Test whether address already in hash table */
328         key = g_utf8_strdown( address, -1 );
329         email = g_hash_table_lookup( muttFile->uniqTable, key );
330
331         if( email == NULL ) {
332                 /* No - create person */
333                 person = addritem_create_item_person();
334                 addritem_person_set_common_name( person, name );
335                 addrcache_id_person( cache, person );
336                 addrcache_add_person( cache, person );
337
338                 /* Add email for person */
339                 email = addritem_create_item_email();
340                 addritem_email_set_address( email, address );
341                 addrcache_id_email( cache, email );
342                 addrcache_person_add_email( cache, person, email );
343
344                 /* Insert entry */
345                 g_hash_table_insert( muttFile->uniqTable, key, email );
346         }
347         else {
348                 /* Yes - update person with longest name */
349                 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
350                 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
351                         addritem_person_set_common_name( person, name );
352                 }
353
354                 /* Free up */
355                 g_free( key );
356         }
357
358         return email;
359 }
360
361 /*
362  * Build address book entries.
363  * Enter: muttFile  MUTT control data.
364  *        cache     Address cache.
365  *        aliasName Alias,
366  *        listAddr  List of address items.
367  *        addrCount Address list count.
368  */
369 static void mutt_build_address(
370                 MuttFile *muttFile, AddressCache *cache,
371                 gchar *aliasName, GSList *listAddr, gint addrCount )
372 {
373         GSList *node = NULL;
374         ItemEMail *email;
375         ItemGroup *group;
376         Mutt_ParsedRec *rec;
377
378         group = NULL;
379         if( listAddr != NULL && addrCount > 1 ) {
380                 group = addritem_create_item_group();
381                 addritem_group_set_name( group, aliasName );
382                 addrcache_id_group( cache, group );
383                 addrcache_add_group( cache, group );
384         }
385
386         email = NULL;
387         node = listAddr;
388         while( node ) {
389                 rec = node->data;
390
391                 /* Insert person/email */
392                 email = mutt_insert_table(
393                                 muttFile, cache, rec->address, rec->name );
394
395                 /* Add email to group */
396                 if( group ) {
397                         addritem_group_add_email( group, email );
398                 }
399
400                 mutt_free_rec( rec );
401                 node = g_slist_next( node );
402         }
403 }
404
405 /*
406  * Parse address line adn build address items.
407  * Enter: muttFile MUTT control data.
408  *        cache    Address cache.
409  *        line     Data record.
410  */
411 static void mutt_build_items( MuttFile *muttFile, AddressCache *cache, gchar *line ) {
412         GList *list, *node;
413         gint tCount, aCount;
414         gchar *aliasTag, *aliasName, *recipient;
415         GSList *addrList;
416
417         /* g_print( "\nBUILD >%s<\n", line ); */
418         list = mgu_parse_string( line,  3, &tCount );
419         if( tCount < 3 ) {
420                 if( list ) {
421                         g_list_free_full( list, g_free );
422                         list = NULL;
423                 }
424                 return;
425         }
426
427         aliasTag = list->data;
428         node = g_list_next( list );
429         aliasName = node->data;
430         node = g_list_next( node );
431         recipient = node->data;
432
433         addrList = NULL;
434         if( strcmp( aliasTag, MUTT_TAG_ALIAS ) == 0 ) {
435                 aCount = 0;
436                 /* g_print( "aliasName :%s:\n", aliasName ); */
437                 /* g_print( "recipient :%s:\n", recipient ); */
438                 addrList = mutt_parse_rcplist( recipient, &aCount );
439                 /* g_print( "---\n" ); */
440                 mutt_build_address( muttFile, cache, aliasName, addrList, aCount );
441         }
442
443         g_list_free_full( list, g_free );
444         list = NULL;
445
446 }
447
448 /*
449  * Read file data into address cache.
450  * Enter: muttFile MUTT control data.
451  *        cache Address cache.
452  */
453 static void mutt_read_file( MuttFile *muttFile, AddressCache *cache ) {
454         GSList *listValue = NULL;
455         gboolean flagEOF = FALSE, flagCont = FALSE, lastCont = FALSE;
456         gchar *line =  NULL, *lineValue = NULL;
457         long posEnd = 0L;
458         long posCur = 0L;
459
460         /* Find EOF for progress indicator */
461         fseek( muttFile->file, 0L, SEEK_END );
462         posEnd = ftell( muttFile->file );
463         fseek( muttFile->file, 0L, SEEK_SET );
464
465         while( ! flagEOF ) {
466                 flagCont = FALSE;
467                 line =  mutt_get_line( muttFile, &flagCont );
468
469                 posCur = ftell( muttFile->file );
470                 if( muttFile->cbProgress ) {
471                         /* Call progress indicator */
472                         ( muttFile->cbProgress ) ( muttFile, & posEnd, & posCur );
473                 }
474
475                 if( line == NULL ) flagEOF = TRUE;
476                 if( ! lastCont ) {
477                         /* Save data */
478                         lineValue = mgu_list_coalesce( listValue );
479                         if( lineValue ) {
480                                 mutt_build_items( muttFile, cache, lineValue );
481                         }
482                         g_free( lineValue );
483                         lineValue = NULL;
484                         g_slist_free_full( listValue, g_free );
485                         listValue = NULL;
486                 }
487                 lastCont = flagCont;
488
489                 /* Add line to list */
490                 listValue = g_slist_append( listValue, g_strdup( line ) );
491
492                 g_free( line );
493                 line = NULL;
494         }
495
496         /* Release data */
497         g_slist_free_full( listValue, g_free );
498         listValue = NULL;
499 }
500
501 /*
502 * ============================================================================================
503 * Read file into list. Main entry point
504 * Enter:  muttFile MUTT control data.
505 *         cache    Address cache to load.
506 * Return: Status code.
507 * ============================================================================================
508 */
509 gint mutt_import_data( MuttFile *muttFile, AddressCache *cache ) {
510         cm_return_val_if_fail( muttFile != NULL, MGU_BAD_ARGS );
511         cm_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
512         muttFile->retVal = MGU_SUCCESS;
513         addrcache_clear( cache );
514         cache->dataRead = FALSE;
515         mutt_open_file( muttFile );
516         if( muttFile->retVal == MGU_SUCCESS ) {
517                 /* Read data into the cache */
518                 mutt_read_file( muttFile, cache );
519                 mutt_close_file( muttFile );
520
521                 /* Mark cache */
522                 cache->modified = FALSE;
523                 cache->dataRead = TRUE;
524         }
525         return muttFile->retVal;
526 }
527
528 #define WORK_BUFLEN 1024
529
530 /*
531 * Attempt to find a Mutt file.
532 * Return: Filename, or home directory if not found, or empty string if
533 * no home. Filename should be g_free() when done.
534 */
535 gchar *mutt_find_file( void ) {
536         const gchar *homedir;
537         gchar str[ WORK_BUFLEN + 1 ];
538         gint len;
539         FILE *fp;
540
541         homedir = get_home_dir();
542         if( ! homedir ) return g_strdup( "" );
543
544         strncpy( str, homedir, WORK_BUFLEN );
545         len = strlen( str );
546         if( len > 0 ) {
547                 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
548                         str[ len ] = G_DIR_SEPARATOR;
549                         str[ ++len ] = '\0';
550                 }
551         }
552         strncat( str, MUTT_HOME_FILE, WORK_BUFLEN - strlen(str) );
553
554         /* Attempt to open */
555         if( ( fp = claws_fopen( str, "rb" ) ) != NULL ) {
556                 claws_fclose( fp );
557         }
558         else {
559                 /* Truncate filename */
560                 str[ len ] = '\0';
561         }
562         return g_strdup( str );
563 }
564
565 /*
566 * End of Source.
567 */
568