Fix bug #3574: Template addressing
[claws.git] / src / pine.c
blob5d29ee51e9869a502e82d24ed8d2b743be239dba
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2012 Match Grun and the Claws Mail team
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.
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.
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/>.
21 * Functions necessary to access Pine address book file.
24 #include <sys/stat.h>
25 #include <glib.h>
26 #include <string.h>
28 #include "utils.h"
29 #include "mgutils.h"
30 #include "pine.h"
31 #include "addritem.h"
32 #include "addrcache.h"
34 #define PINE_HOME_FILE ".addressbook"
35 #define PINEBUFSIZE 2048
36 #define CHAR_QUOTE '\"'
37 #define CHAR_APOS '\''
38 #define CHAR_COMMA ','
39 #define CHAR_AT '@'
42 * Create new object.
44 PineFile *pine_create() {
45 PineFile *pineFile;
46 pineFile = g_new0( PineFile, 1 );
47 pineFile->path = NULL;
48 pineFile->file = NULL;
49 pineFile->retVal = MGU_SUCCESS;
50 pineFile->uniqTable = g_hash_table_new( g_str_hash, g_str_equal );
51 pineFile->cbProgress = NULL;
52 return pineFile;
56 * Properties...
58 void pine_set_file( PineFile* pineFile, const gchar *value ) {
59 cm_return_if_fail( pineFile != NULL );
60 pineFile->path = mgu_replace_string( pineFile->path, value );
61 g_strstrip( pineFile->path );
65 * Free key in table.
67 static gint pine_free_table_vis( gpointer key, gpointer value, gpointer data ) {
68 g_free( key );
69 return TRUE;
73 * Free up object by releasing internal memory.
75 void pine_free( PineFile *pineFile ) {
76 cm_return_if_fail( pineFile != NULL );
78 /* Close file */
79 if( pineFile->file ) fclose( pineFile->file );
81 /* Free internal stuff */
82 g_free( pineFile->path );
84 /* Free unique address table */
85 g_hash_table_foreach_remove( pineFile->uniqTable, pine_free_table_vis, NULL );
86 g_hash_table_destroy( pineFile->uniqTable );
88 /* Clear pointers */
89 pineFile->file = NULL;
90 pineFile->path = NULL;
91 pineFile->retVal = MGU_SUCCESS;
92 pineFile->uniqTable = NULL;
93 pineFile->cbProgress = NULL;
95 /* Now release file object */
96 g_free( pineFile );
100 * Open file for read.
101 * Enter: pineFile File object.
102 * return: TRUE if file opened successfully.
104 static gint pine_open_file( PineFile* pineFile ) {
105 if( pineFile->path ) {
106 pineFile->file = g_fopen( pineFile->path, "rb" );
107 if( ! pineFile->file ) {
108 pineFile->retVal = MGU_OPEN_FILE;
109 return pineFile->retVal;
112 else {
113 /* g_print( "file not specified\n" ); */
114 pineFile->retVal = MGU_NO_FILE;
115 return pineFile->retVal;
118 /* Setup a buffer area */
119 pineFile->retVal = MGU_SUCCESS;
120 return pineFile->retVal;
124 * Close file.
125 * Enter: pineFile File object.
127 static void pine_close_file( PineFile *pineFile ) {
128 cm_return_if_fail( pineFile != NULL );
129 if( pineFile->file ) fclose( pineFile->file );
130 pineFile->file = NULL;
134 * Read line of text from file.
135 * Enter: pineFile File object.
136 * Return: Copy of buffer. Should be g_free'd when done.
138 static gchar *pine_read_line( PineFile *pineFile ) {
139 gchar buf[ PINEBUFSIZE ];
140 int c, i = 0;
141 gchar ch;
143 if( feof( pineFile->file ) )
144 return NULL;
146 while( i < PINEBUFSIZE-1 ) {
147 c = fgetc( pineFile->file );
148 if( c == EOF ) {
149 if( i == 0 )
150 return NULL;
151 break;
153 ch = (gchar) c;
154 if( ch == '\0' ) {
155 if( i == 0 )
156 return NULL;
157 break;
159 if( ch == '\n' ) {
160 break;
162 buf[i] = ch;
163 i++;
165 buf[i] = '\0';
167 /* Copy into private buffer */
168 return g_strdup( buf );
172 * Parsed address data.
174 typedef struct _Pine_ParsedRec_ Pine_ParsedRec;
175 struct _Pine_ParsedRec_ {
176 gchar *nickName;
177 gchar *name;
178 gchar *address;
179 gchar *fcc;
180 gchar *comments;
181 gboolean isGroup;
182 GSList *listName;
183 GSList *listAddr;
187 * Free data record.
188 * Enter: rec Data record.
190 static void pine_free_rec( Pine_ParsedRec *rec ) {
191 if( rec ) {
192 g_free( rec->nickName );
193 g_free( rec->name );
194 g_free( rec->address );
195 g_free( rec->fcc );
196 g_free( rec->comments );
197 mgu_clear_slist( rec->listName );
198 mgu_clear_slist( rec->listAddr );
199 g_slist_free( rec->listName );
200 g_slist_free( rec->listAddr );
201 rec->nickName = NULL;
202 rec->name = NULL;
203 rec->address = NULL;
204 rec->fcc = NULL;
205 rec->comments = NULL;
206 rec->isGroup = FALSE;
207 g_free( rec );
212 * Clean name.
214 static void pine_clean_name( Pine_ParsedRec *rec ) {
215 gchar *p;
217 p = rec->name;
218 if( p == NULL ) return;
219 if( *p == '\0' ) return;
221 g_strstrip( rec->name );
222 if( *p == CHAR_APOS || *p == CHAR_QUOTE ) {
223 return;
226 /* If embedded comma present, surround match with quotes */
227 while( *p ) {
228 if( *p == CHAR_COMMA ) {
229 p = g_strdup_printf( "\"%s\"", rec->name );
230 g_free( rec->name );
231 rec->name = p;
232 return;
234 p++;
239 * Parse pine address record.
240 * Enter: buf Address record buffer.
241 * Return: Data record.
243 static Pine_ParsedRec *pine_parse_record( gchar *buf ) {
244 Pine_ParsedRec *rec;
245 gchar *p, *f;
246 gint pos, len, i;
247 gchar *tmp[5];
249 for( i = 0; i < 5; i++ )
250 tmp[i] = NULL;
252 /* Extract tab separated values */
253 rec = NULL;
254 pos = 0;
255 p = f = buf;
256 while( *p ) {
257 if( *p == '\t' ) {
258 len = p - f;
259 if( len > 0 ) {
260 tmp[ pos ] = g_strndup( f, len );
261 f = p;
262 f++;
264 pos++;
266 p++;
269 /* Extract last value */
270 len = p - f;
271 if( len > 0 ) {
272 tmp[ pos++ ] = g_strndup( f, len );
275 /* Populate record */
276 if( pos > 0 ) {
277 rec = g_new0( Pine_ParsedRec, 1 );
278 rec->isGroup = FALSE;
279 for( i = 0; i < pos; i++ ) {
280 f = tmp[i];
281 if( f ) {
282 g_strstrip( f );
284 if( i == 0 ) rec->nickName = f;
285 else if( i == 1 ) rec->name = f;
286 else if( i == 2 ) rec->address = f;
287 else if( i == 3 ) rec->fcc = f;
288 else if( i == 4 ) rec->comments = f;
289 tmp[i] = NULL;
292 if( rec->address != NULL ) {
293 /* Strip leading/trailing parens */
294 p = rec->address;
295 if( *p == '(' ) {
296 len = strlen( p ) - 1;
297 *p = ' ';
298 *(p + len) = ' ';
299 rec->isGroup = TRUE;
304 return rec;
308 * Parse name from email address string.
309 * Enter: buf Start address of buffer to process (not modified).
310 * atp Pointer to email at (@) character.
311 * ap Pointer to start of email address returned.
312 * ep Pointer to end of email address returned.
313 * Return: Parsed name or NULL if not present. This should be g_free'd
314 * when done.
316 static gchar *pine_parse_name(
317 const gchar *buf, const gchar *atp, const gchar **ap,
318 const gchar **ep )
320 gchar *name;
321 const gchar *pos;
322 const gchar *tmp;
323 const gchar *bp;
324 gint ilen;
326 name = NULL;
327 *ap = NULL;
328 *ep = NULL;
330 /* Find first non-separator char */
331 bp = buf;
332 while( TRUE ) {
333 if( strchr( ",; \n\r", *bp ) == NULL ) break;
334 bp++;
337 /* Search back for start of name */
338 tmp = atp;
339 pos = atp;
340 while( pos >= bp ) {
341 tmp = pos;
342 if( *pos == '<' ) {
343 /* Found start of address/end of name part */
344 ilen = -1 + ( size_t ) ( pos - bp );
345 name = g_strndup( bp, ilen + 1 );
346 *(name + ilen + 1) = '\0';
348 /* Remove leading trailing quotes and spaces */
349 mgu_str_ltc2space( name, '\"', '\"' );
350 mgu_str_ltc2space( name, '\'', '\'' );
351 mgu_str_ltc2space( name, '\"', '\"' );
352 mgu_str_unescape( name );
353 g_strstrip( name );
354 break;
356 pos--;
358 *ap = tmp;
360 /* Search forward for end of address */
361 pos = atp + 1;
362 while( TRUE ) {
363 if( *pos == '>' ) {
364 pos++;
365 break;
367 if( strchr( ",; \'\n\r", *pos ) ) break;
368 pos++;
370 *ep = pos;
372 return name;
376 * Parse address list.
377 * Enter: pineFile Pine control data.
378 * cache Address cache.
379 * rec Data record.
381 static void pine_parse_address( PineFile *pineFile, AddressCache *cache, Pine_ParsedRec *rec ) {
382 const gchar *buf;
383 gchar addr[ PINEBUFSIZE ];
384 const gchar *bp;
385 const gchar *ep;
386 gchar *atCh;
387 gchar *name;
388 gint len;
390 cm_return_if_fail( rec->address != NULL );
392 buf = rec->address;
393 while((atCh = strchr( buf, CHAR_AT )) != NULL) {
394 name = pine_parse_name( buf, atCh, &bp, &ep );
395 len = ( size_t ) ( ep - bp );
396 strncpy( addr, bp, len );
397 addr[ len ] = '\0';
398 extract_address( addr );
400 if( name == NULL ) name = g_strdup( "" );
401 rec->listName = g_slist_append( rec->listName, name );
402 rec->listAddr = g_slist_append( rec->listAddr, g_strdup( addr ) );
404 buf = ep;
405 if( atCh == ep ) {
406 buf++;
412 * Insert person and address into address cache.
413 * Enter: pineFile Pine control data.
414 * cache Address cache.
415 * address E-Mail address.
416 * name Name.
417 * remarks Remarks.
418 * Return: E-Mail object, either inserted or found in hash table.
420 static ItemEMail *pine_insert_table(
421 PineFile *pineFile, AddressCache *cache, gchar *address,
422 gchar *name, gchar *remarks )
424 ItemPerson *person;
425 ItemEMail *email;
426 gchar *key;
428 cm_return_val_if_fail( address != NULL, NULL );
430 /* create an entry with empty name if needed */
431 if ( name == NULL )
432 name = "";
434 /* Test whether address already in hash table */
435 key = g_utf8_strdown( address, -1 );
436 email = g_hash_table_lookup( pineFile->uniqTable, key );
438 if( email == NULL ) {
439 /* No - create person */
440 person = addritem_create_item_person();
441 addritem_person_set_common_name( person, name );
442 addrcache_id_person( cache, person );
443 addrcache_add_person( cache, person );
445 /* Add email for person */
446 email = addritem_create_item_email();
447 addritem_email_set_address( email, address );
448 addritem_email_set_remarks( email, remarks );
449 addrcache_id_email( cache, email );
450 addrcache_person_add_email( cache, person, email );
452 /* Insert entry */
453 g_hash_table_insert( pineFile->uniqTable, key, email );
455 else {
456 /* Yes - update person with longest name */
457 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
458 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
459 addritem_person_set_common_name( person, name );
462 /* Free up */
463 g_free( key );
466 return email;
470 * Parse address line adn build address items.
471 * Enter: pineFile Pine control data.
472 * cache Address cache to load.
473 * line Address record.
475 static void pine_build_items( PineFile *pineFile, AddressCache *cache, gchar *line ) {
476 Pine_ParsedRec *rec;
477 GSList *nodeAddr, *nodeName;
478 ItemGroup *group;
479 ItemEMail *email;
481 rec = pine_parse_record( line );
482 if( rec ) {
483 pine_clean_name( rec );
484 pine_parse_address( pineFile, cache, rec );
485 /* pine_print_rec( rec, stdout ); */
486 /* g_print( "=========\n" ); */
488 if( rec->isGroup ) {
489 /* Create group */
490 group = addritem_create_item_group();
491 addritem_group_set_name( group, rec->nickName );
492 addrcache_id_group( cache, group );
493 addrcache_add_group( cache, group );
495 /* Add email to group */
496 nodeName = rec->listName;
497 nodeAddr = rec->listAddr;
498 while( nodeAddr ) {
499 email = pine_insert_table(
500 pineFile, cache, nodeAddr->data,
501 nodeName->data, "" );
503 /* Add email to group */
504 addritem_group_add_email( group, email );
506 nodeAddr = g_slist_next( nodeAddr );
507 nodeName = g_slist_next( nodeName );
510 else {
511 pine_insert_table(
512 pineFile, cache, rec->address,
513 rec->name, rec->comments );
516 pine_free_rec( rec );
521 * Read file data into address cache.
522 * Enter: pineFile Pine control data.
523 * cache Address cache to load.
525 static void pine_read_file( PineFile *pineFile, AddressCache *cache ) {
526 GSList *listValue = NULL;
527 gboolean flagEOF = FALSE, flagProc = FALSE, flagDone = FALSE;
528 gchar *line = NULL, *lineValue = NULL;
529 long posEnd = 0L;
530 long posCur = 0L;
532 /* Find EOF for progress indicator */
533 fseek( pineFile->file, 0L, SEEK_END );
534 posEnd = ftell( pineFile->file );
535 fseek( pineFile->file, 0L, SEEK_SET );
537 flagProc = FALSE;
538 while( ! flagDone ) {
539 if( flagEOF ) {
540 flagDone = TRUE;
541 flagProc = TRUE;
543 else {
544 line = pine_read_line( pineFile );
547 posCur = ftell( pineFile->file );
548 if( pineFile->cbProgress ) {
549 /* Call progress indicator */
550 ( pineFile->cbProgress ) ( pineFile, & posEnd, & posCur );
553 /* Add line to list */
554 if( line == NULL ) {
555 flagEOF = TRUE;
557 else {
558 /* Check for continuation line (1 space only) */
559 if( *line == ' ' ) {
560 g_strchug( line );
561 listValue = g_slist_append(
562 listValue, g_strdup( line ) );
563 flagProc = FALSE;
565 else {
566 flagProc = TRUE;
570 if( flagProc ) {
571 if( listValue != NULL ) {
572 /* Process list */
573 lineValue = mgu_list_coalesce( listValue );
574 if( lineValue ) {
575 pine_build_items(
576 pineFile, cache, lineValue );
578 g_free( lineValue );
579 lineValue = NULL;
580 mgu_free_list( listValue );
581 listValue = NULL;
583 if( line != NULL ) {
584 /* Append to list */
585 listValue = g_slist_append(
586 listValue, g_strdup( line ) );
590 g_free( line );
591 line = NULL;
594 /* Release data */
595 mgu_free_list( listValue );
596 listValue = NULL;
600 * ============================================================================================
601 * Read file into list. Main entry point
602 * Enter: pineFile Pine control data.
603 * cache Address cache to load.
604 * Return: Status code.
605 * ============================================================================================
607 gint pine_import_data( PineFile *pineFile, AddressCache *cache ) {
608 cm_return_val_if_fail( pineFile != NULL, MGU_BAD_ARGS );
609 cm_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
611 pineFile->retVal = MGU_SUCCESS;
612 addrcache_clear( cache );
613 cache->dataRead = FALSE;
614 pine_open_file( pineFile );
615 if( pineFile->retVal == MGU_SUCCESS ) {
616 /* Read data into the cache */
617 pine_read_file( pineFile, cache );
618 pine_close_file( pineFile );
620 /* Mark cache */
621 cache->modified = FALSE;
622 cache->dataRead = TRUE;
624 return pineFile->retVal;
627 #define WORK_BUFLEN 1024
630 * Attempt to find a Pine addressbook file.
631 * Return: Filename, or home directory if not found, or empty string if
632 * no home. Filename should be g_free() when done.
634 gchar *pine_find_file( void ) {
635 const gchar *homedir;
636 gchar str[ WORK_BUFLEN + 1 ];
637 gint len;
638 FILE *fp;
640 homedir = get_home_dir();
641 if( ! homedir ) return g_strdup( "" );
643 strncpy( str, homedir, WORK_BUFLEN );
644 len = strlen( str );
645 if( len > 0 ) {
646 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
647 str[ len ] = G_DIR_SEPARATOR;
648 str[ ++len ] = '\0';
651 strncat( str, PINE_HOME_FILE, WORK_BUFLEN - strlen(str) );
653 /* Attempt to open */
654 if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
655 fclose( fp );
657 else {
658 /* Truncate filename */
659 str[ len ] = '\0';
661 return g_strdup( str );
665 * End of Source.