1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002-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 Pine address book file.
22  */
23 
24 #include <sys/stat.h>
25 #include <glib.h>
26 #include <string.h>
27 
28 #include "utils.h"
29 #include "mgutils.h"
30 #include "pine.h"
31 #include "addritem.h"
32 #include "addrcache.h"
33 #include "file-utils.h"
34 
35 #define PINE_HOME_FILE  ".addressbook"
36 #define PINEBUFSIZE     2048
37 #define CHAR_QUOTE      '\"'
38 #define CHAR_APOS       '\''
39 #define CHAR_COMMA      ','
40 #define CHAR_AT         '@'
41 
42 /*
43 * Create new object.
44 */
pine_create()45 PineFile *pine_create() {
46 	PineFile *pineFile;
47 	pineFile = g_new0( PineFile, 1 );
48 	pineFile->path = NULL;
49 	pineFile->file = NULL;
50 	pineFile->retVal = MGU_SUCCESS;
51 	pineFile->uniqTable = g_hash_table_new( g_str_hash, g_str_equal );
52 	pineFile->cbProgress = NULL;
53 	return pineFile;
54 }
55 
56 /*
57 * Properties...
58 */
pine_set_file(PineFile * pineFile,const gchar * value)59 void pine_set_file( PineFile* pineFile, const gchar *value ) {
60 	cm_return_if_fail( pineFile != NULL );
61 	pineFile->path = mgu_replace_string( pineFile->path, value );
62 	g_strstrip( pineFile->path );
63 }
64 
65 /*
66  * Free key in table.
67  */
pine_free_table_vis(gpointer key,gpointer value,gpointer data)68 static gint pine_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 */
pine_free(PineFile * pineFile)76 void pine_free( PineFile *pineFile ) {
77 	cm_return_if_fail( pineFile != NULL );
78 
79 	/* Close file */
80 	if( pineFile->file ) claws_fclose( pineFile->file );
81 
82 	/* Free internal stuff */
83 	g_free( pineFile->path );
84 
85 	/* Free unique address table */
86 	g_hash_table_foreach_remove( pineFile->uniqTable, pine_free_table_vis, NULL );
87 	g_hash_table_destroy( pineFile->uniqTable );
88 
89 	/* Clear pointers */
90 	pineFile->file = NULL;
91 	pineFile->path = NULL;
92 	pineFile->retVal = MGU_SUCCESS;
93 	pineFile->uniqTable = NULL;
94 	pineFile->cbProgress = NULL;
95 
96 	/* Now release file object */
97 	g_free( pineFile );
98 }
99 
100 /*
101  * Open file for read.
102  * Enter: pineFile File object.
103  * return: TRUE if file opened successfully.
104  */
pine_open_file(PineFile * pineFile)105 static gint pine_open_file( PineFile* pineFile ) {
106 	if( pineFile->path ) {
107 		pineFile->file = claws_fopen( pineFile->path, "rb" );
108 		if( ! pineFile->file ) {
109 			pineFile->retVal = MGU_OPEN_FILE;
110 			return pineFile->retVal;
111 		}
112 	}
113 	else {
114 		/* g_print( "file not specified\n" ); */
115 		pineFile->retVal = MGU_NO_FILE;
116 		return pineFile->retVal;
117 	}
118 
119 	/* Setup a buffer area */
120 	pineFile->retVal = MGU_SUCCESS;
121 	return pineFile->retVal;
122 }
123 
124 /*
125  * Close file.
126  * Enter: pineFile File object.
127  */
pine_close_file(PineFile * pineFile)128 static void pine_close_file( PineFile *pineFile ) {
129 	cm_return_if_fail( pineFile != NULL );
130 	if( pineFile->file ) claws_fclose( pineFile->file );
131 	pineFile->file = NULL;
132 }
133 
134 /*
135  * Read line of text from file.
136  * Enter: pineFile File object.
137  * Return: Copy of buffer. Should be g_free'd when done.
138  */
pine_read_line(PineFile * pineFile)139 static gchar *pine_read_line( PineFile *pineFile ) {
140 	gchar buf[ PINEBUFSIZE ];
141 	int c, i = 0;
142 	gchar ch;
143 
144 	if( claws_feof( pineFile->file ) )
145 		return NULL;
146 
147 	while( i < PINEBUFSIZE-1 ) {
148 		c = fgetc( pineFile->file );
149 		if( c == EOF ) {
150 			if( i == 0 )
151 				return NULL;
152 			break;
153 		}
154 		ch = (gchar) c;
155 		if( ch == '\0' ) {
156 			if( i == 0 )
157 				return NULL;
158 			break;
159 		}
160 		if( ch == '\n' ) {
161 			break;
162 		}
163 		buf[i] = ch;
164 		i++;
165 	}
166 	buf[i] = '\0';
167 
168 	/* Copy into private buffer */
169 	return g_strdup( buf );
170 }
171 
172 /*
173  * Parsed address data.
174  */
175 typedef struct _Pine_ParsedRec_ Pine_ParsedRec;
176 struct _Pine_ParsedRec_ {
177 	gchar *nickName;
178 	gchar *name;
179 	gchar *address;
180 	gchar *fcc;
181 	gchar *comments;
182 	gboolean isGroup;
183 	GSList *listName;
184 	GSList *listAddr;
185 };
186 
187 /*
188  * Free data record.
189  * Enter: rec Data record.
190  */
pine_free_rec(Pine_ParsedRec * rec)191 static void pine_free_rec( Pine_ParsedRec *rec ) {
192 	if( rec ) {
193 		g_free( rec->nickName );
194 		g_free( rec->name );
195 		g_free( rec->address );
196 		g_free( rec->fcc );
197 		g_free( rec->comments );
198 		g_slist_free_full( rec->listName, g_free );
199 		g_slist_free_full( rec->listAddr, g_free );
200 		rec->nickName = NULL;
201 		rec->name = NULL;
202 		rec->address = NULL;
203 		rec->fcc = NULL;
204 		rec->comments = NULL;
205 		rec->isGroup = FALSE;
206 		g_free( rec );
207 	}
208 }
209 
210 /*
211  * Clean name.
212  */
pine_clean_name(Pine_ParsedRec * rec)213 static void pine_clean_name( Pine_ParsedRec *rec ) {
214 	gchar *p;
215 
216 	p = rec->name;
217 	if( p == NULL ) return;
218 	if( *p == '\0' ) return;
219 
220 	g_strstrip( rec->name );
221 	if( *p == CHAR_APOS || *p == CHAR_QUOTE ) {
222 		return;
223 	}
224 
225 	/* If embedded comma present, surround match with quotes */
226 	while( *p ) {
227 		if( *p == CHAR_COMMA ) {
228 			p = g_strdup_printf( "\"%s\"", rec->name );
229 			g_free( rec->name );
230 			rec->name = p;
231 			return;
232 		}
233 		p++;
234 	}
235 }
236 
237 /*
238  * Parse pine address record.
239  * Enter:  buf Address record buffer.
240  * Return: Data record.
241  */
pine_parse_record(gchar * buf)242 static Pine_ParsedRec *pine_parse_record( gchar *buf ) {
243 	Pine_ParsedRec *rec;
244 	gchar *p, *f;
245 	gint pos, len, i;
246 	gchar *tmp[5];
247 
248 	for( i = 0; i < 5; i++ )
249 		tmp[i] = NULL;
250 
251 	/* Extract tab separated values */
252 	rec = NULL;
253 	pos = 0;
254 	p = f = buf;
255 	while( *p ) {
256 		if( *p == '\t' ) {
257 			len = p - f;
258 			if( len > 0 ) {
259 				tmp[ pos ] = g_strndup( f, len );
260 				f = p;
261 				f++;
262 			}
263 			pos++;
264 		}
265 		p++;
266 	}
267 
268 	/* Extract last value */
269 	len = p - f;
270 	if( len > 0 ) {
271 		tmp[ pos++ ] = g_strndup( f, len );
272 	}
273 
274 	/* Populate record */
275 	if( pos > 0 ) {
276 		rec = g_new0( Pine_ParsedRec, 1 );
277 		rec->isGroup = FALSE;
278 		for( i = 0; i < pos; i++ ) {
279 			f = tmp[i];
280 			if( f ) {
281 				g_strstrip( f );
282 			}
283 			if( i == 0 ) rec->nickName = f;
284 			else if( i == 1 ) rec->name = f;
285 			else if( i == 2 ) rec->address = f;
286 			else if( i == 3 ) rec->fcc = f;
287 			else if( i == 4 ) rec->comments = f;
288 			tmp[i] = NULL;
289 		}
290 
291 		if( rec->address != NULL ) {
292 			/* Strip leading/trailing parens */
293 			p = rec->address;
294 			if( *p == '(' ) {
295 				len = strlen( p ) - 1;
296 				*p = ' ';
297 				*(p + len) = ' ';
298 				rec->isGroup = TRUE;
299 			}
300 		}
301 	}
302 
303 	return rec;
304 }
305 
306 /*
307  * Parse name from email address string.
308  * Enter: buf Start address of buffer to process (not modified).
309  *        atp Pointer to email at (@) character.
310  *        ap  Pointer to start of email address returned.
311  *        ep  Pointer to end of email address returned.
312  * Return: Parsed name or NULL if not present. This should be g_free'd
313  * when done.
314  */
pine_parse_name(const gchar * buf,const gchar * atp,const gchar ** ap,const gchar ** ep)315 static gchar *pine_parse_name(
316 		const gchar *buf, const gchar *atp, const gchar **ap,
317 		const gchar **ep )
318 {
319 	gchar *name;
320 	const gchar *pos;
321 	const gchar *tmp;
322 	const gchar *bp;
323 	gint ilen;
324 
325 	name = NULL;
326 	*ap = NULL;
327 	*ep = NULL;
328 
329 	/* Find first non-separator char */
330 	bp = buf;
331 	while( TRUE ) {
332 		if( strchr( ",; \n\r", *bp ) == NULL ) break;
333 		bp++;
334 	}
335 
336 	/* Search back for start of name */
337 	tmp = atp;
338 	pos = atp;
339 	while( pos >= bp ) {
340 		tmp = pos;
341 		if( *pos == '<' ) {
342 			/* Found start of address/end of name part */
343 			ilen = -1 + ( size_t ) ( pos - bp );
344 			name = g_strndup( bp, ilen + 1 );
345 			*(name + ilen + 1) = '\0';
346 
347 			/* Remove leading trailing quotes and spaces */
348 			mgu_str_ltc2space( name, '\"', '\"' );
349 			mgu_str_ltc2space( name, '\'', '\'' );
350 			mgu_str_ltc2space( name, '\"', '\"' );
351 			mgu_str_unescape( name );
352 			g_strstrip( name );
353 			break;
354 		}
355 		pos--;
356 	}
357 	*ap = tmp;
358 
359 	/* Search forward for end of address */
360 	pos = atp + 1;
361 	while( TRUE ) {
362 		if( *pos == '>' ) {
363 			pos++;
364 			break;
365 		}
366 		if( strchr( ",; \'\n\r", *pos ) ) break;
367 		pos++;
368 	}
369 	*ep = pos;
370 
371 	return name;
372 }
373 
374 /*
375  * Parse address list.
376  * Enter: pineFile Pine control data.
377  *        cache    Address cache.
378  *        rec      Data record.
379  */
pine_parse_address(PineFile * pineFile,AddressCache * cache,Pine_ParsedRec * rec)380 static void pine_parse_address( PineFile *pineFile, AddressCache *cache, Pine_ParsedRec *rec ) {
381 	const gchar *buf;
382 	gchar addr[ PINEBUFSIZE ];
383 	const gchar *bp;
384 	const gchar *ep;
385 	gchar *atCh;
386 	gchar *name;
387 	gint len;
388 
389 	cm_return_if_fail( rec->address != NULL );
390 
391 	buf = rec->address;
392 	while((atCh = strchr( buf, CHAR_AT )) != NULL) {
393 		name = pine_parse_name( buf, atCh, &bp, &ep );
394 		len = ( size_t ) ( ep - bp );
395 		strncpy( addr, bp, len );
396 		addr[ len ] = '\0';
397 		extract_address( addr );
398 
399 		if( name == NULL ) name = g_strdup( "" );
400 		rec->listName = g_slist_append( rec->listName, name );
401 		rec->listAddr = g_slist_append( rec->listAddr, g_strdup( addr ) );
402 
403 		buf = ep;
404 		if( atCh == ep ) {
405 			buf++;
406 		}
407 	}
408 }
409 
410 /*
411  * Insert person and address into address cache.
412  * Enter: pineFile Pine control data.
413  *        cache    Address cache.
414  *        address  E-Mail address.
415  *        name     Name.
416  *        remarks  Remarks.
417  * Return: E-Mail object, either inserted or found in hash table.
418  */
pine_insert_table(PineFile * pineFile,AddressCache * cache,gchar * address,gchar * name,gchar * remarks)419 static ItemEMail *pine_insert_table(
420 		PineFile *pineFile, AddressCache *cache, gchar *address,
421 		gchar *name, gchar *remarks )
422 {
423 	ItemPerson *person;
424 	ItemEMail *email;
425 	gchar *key;
426 
427 	cm_return_val_if_fail( address != NULL, NULL );
428 
429 	/* create an entry with empty name if needed */
430 	if ( name == NULL )
431 		name = "";
432 
433 	/* Test whether address already in hash table */
434 	key = g_utf8_strdown( address, -1 );
435 	email = g_hash_table_lookup( pineFile->uniqTable, key );
436 
437 	if( email == NULL ) {
438 		/* No - create person */
439 		person = addritem_create_item_person();
440 		addritem_person_set_common_name( person, name );
441 		addrcache_id_person( cache, person );
442 		addrcache_add_person( cache, person );
443 
444 		/* Add email for person */
445 		email = addritem_create_item_email();
446 		addritem_email_set_address( email, address );
447 		addritem_email_set_remarks( email, remarks );
448 		addrcache_id_email( cache, email );
449 		addrcache_person_add_email( cache, person, email );
450 
451 		/* Insert entry */
452 		g_hash_table_insert( pineFile->uniqTable, key, email );
453 	}
454 	else {
455 		/* Yes - update person with longest name */
456 		person = ( ItemPerson * ) ADDRITEM_PARENT(email);
457 		if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
458 			addritem_person_set_common_name( person, name );
459 		}
460 
461 		/* Free up */
462 		g_free( key );
463 	}
464 
465 	return email;
466 }
467 
468 /*
469  * Parse address line adn build address items.
470  * Enter: pineFile Pine control data.
471  *        cache    Address cache to load.
472  *        line     Address record.
473  */
pine_build_items(PineFile * pineFile,AddressCache * cache,gchar * line)474 static void pine_build_items( PineFile *pineFile, AddressCache *cache, gchar *line ) {
475 	Pine_ParsedRec *rec;
476 	GSList *nodeAddr, *nodeName;
477 	ItemGroup *group;
478 	ItemEMail *email;
479 
480 	rec = pine_parse_record( line );
481 	if( rec ) {
482 		pine_clean_name( rec );
483 		pine_parse_address( pineFile, cache, rec );
484 		/* pine_print_rec( rec, stdout ); */
485 		/* g_print( "=========\n" ); */
486 
487 		if( rec->isGroup ) {
488 			/* Create group */
489 			group = addritem_create_item_group();
490 			addritem_group_set_name( group, rec->nickName );
491 			addrcache_id_group( cache, group );
492 			addrcache_add_group( cache, group );
493 
494 			/* Add email to group */
495 			nodeName = rec->listName;
496 			nodeAddr = rec->listAddr;
497 			while( nodeAddr ) {
498 				email = pine_insert_table(
499 						pineFile, cache, nodeAddr->data,
500 						nodeName->data, "" );
501 
502 				/* Add email to group */
503 				addritem_group_add_email( group, email );
504 
505 				nodeAddr = g_slist_next( nodeAddr );
506 				nodeName = g_slist_next( nodeName );
507 			}
508 		}
509 		else {
510 			pine_insert_table(
511 				pineFile, cache, rec->address,
512 				rec->name, rec->comments );
513 		}
514 
515 		pine_free_rec( rec );
516 	}
517 }
518 
519 /*
520  * Read file data into address cache.
521  * Enter: pineFile Pine control data.
522  *        cache    Address cache to load.
523  */
pine_read_file(PineFile * pineFile,AddressCache * cache)524 static void pine_read_file( PineFile *pineFile, AddressCache *cache ) {
525 	GSList *listValue = NULL;
526 	gboolean flagEOF = FALSE, flagProc = FALSE, flagDone = FALSE;
527 	gchar *line =  NULL, *lineValue = NULL;
528 	long posEnd = 0L;
529 	long posCur = 0L;
530 
531 	/* Find EOF for progress indicator */
532 	fseek( pineFile->file, 0L, SEEK_END );
533 	posEnd = ftell( pineFile->file );
534 	fseek( pineFile->file, 0L, SEEK_SET );
535 
536 	flagProc = FALSE;
537 	while( ! flagDone ) {
538 		if( flagEOF ) {
539 			flagDone = TRUE;
540 			flagProc = TRUE;
541 		}
542 		else {
543 			line =  pine_read_line( pineFile );
544 		}
545 
546 		posCur = ftell( pineFile->file );
547 		if( pineFile->cbProgress ) {
548 			/* Call progress indicator */
549 			( pineFile->cbProgress ) ( pineFile, & posEnd, & posCur );
550 		}
551 
552 		/* Add line to list */
553 		if( line == NULL ) {
554 			flagEOF = TRUE;
555 		}
556 		else {
557 			/* Check for continuation line (1 space only) */
558 			if( *line == ' ' ) {
559 				g_strchug( line );
560 				listValue = g_slist_append(
561 						listValue, g_strdup( line ) );
562 				flagProc = FALSE;
563 			}
564 			else {
565 				flagProc = TRUE;
566 			}
567 		}
568 
569 		if( flagProc ) {
570 			if( listValue != NULL ) {
571 				/* Process list */
572 				lineValue = mgu_list_coalesce( listValue );
573 				if( lineValue ) {
574 					pine_build_items(
575 						pineFile, cache, lineValue );
576 				}
577 				g_free( lineValue );
578 				lineValue = NULL;
579 				g_slist_free_full( listValue, g_free );
580 				listValue = NULL;
581 			}
582 			if( line != NULL ) {
583 				/* Append to list */
584 				listValue = g_slist_append(
585 						listValue, g_strdup( line ) );
586 			}
587 		}
588 
589 		g_free( line );
590 		line = NULL;
591 	}
592 
593 	/* Release data */
594 	g_slist_free_full( listValue, g_free );
595 	listValue = NULL;
596 }
597 
598 /*
599  * ============================================================================================
600  * Read file into list. Main entry point
601  * Enter:  pineFile Pine control data.
602  *         cache    Address cache to load.
603  * Return: Status code.
604  * ============================================================================================
605  */
pine_import_data(PineFile * pineFile,AddressCache * cache)606 gint pine_import_data( PineFile *pineFile, AddressCache *cache ) {
607 	cm_return_val_if_fail( pineFile != NULL, MGU_BAD_ARGS );
608 	cm_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
609 
610 	pineFile->retVal = MGU_SUCCESS;
611 	addrcache_clear( cache );
612 	cache->dataRead = FALSE;
613 	pine_open_file( pineFile );
614 	if( pineFile->retVal == MGU_SUCCESS ) {
615 		/* Read data into the cache */
616 		pine_read_file( pineFile, cache );
617 		pine_close_file( pineFile );
618 
619 		/* Mark cache */
620 		cache->modified = FALSE;
621 		cache->dataRead = TRUE;
622 	}
623 	return pineFile->retVal;
624 }
625 
626 #define WORK_BUFLEN 1024
627 
628 /*
629  * Attempt to find a Pine addressbook file.
630  * Return: Filename, or home directory if not found, or empty string if
631  * no home. Filename should be g_free() when done.
632  */
pine_find_file(void)633 gchar *pine_find_file( void ) {
634 	const gchar *homedir;
635 	gchar str[ WORK_BUFLEN + 1 ];
636 	gint len;
637 	FILE *fp;
638 
639 	homedir = get_home_dir();
640 	if( ! homedir ) return g_strdup( "" );
641 
642 	strncpy( str, homedir, WORK_BUFLEN );
643 	len = strlen( str );
644 	if( len > 0 ) {
645 		if( str[ len-1 ] != G_DIR_SEPARATOR ) {
646 			str[ len ] = G_DIR_SEPARATOR;
647 			str[ ++len ] = '\0';
648 		}
649 	}
650 	strncat( str, PINE_HOME_FILE, WORK_BUFLEN - strlen(str) );
651 
652 	/* Attempt to open */
653 	if( ( fp = claws_fopen( str, "rb" ) ) != NULL ) {
654 		claws_fclose( fp );
655 	}
656 	else {
657 		/* Truncate filename */
658 		str[ len ] = '\0';
659 	}
660 	return g_strdup( str );
661 }
662 
663 /*
664 * End of Source.
665 */
666 
667