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