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 */
mutt_create()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 */
mutt_set_file(MuttFile * muttFile,const gchar * value)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  */
mutt_free_table_vis(gpointer key,gpointer value,gpointer data)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 */
mutt_free(MuttFile * muttFile)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 */
mutt_open_file(MuttFile * muttFile)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 */
mutt_close_file(MuttFile * muttFile)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 */
mutt_get_line(MuttFile * muttFile,gboolean * flagCont)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  */
mutt_free_rec(Mutt_ParsedRec * rec)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 */
mutt_parse_rcplist(gchar * rcpList,gint * addrCount)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  */
mutt_insert_table(MuttFile * muttFile,AddressCache * cache,gchar * address,gchar * name)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  */
mutt_build_address(MuttFile * muttFile,AddressCache * cache,gchar * aliasName,GSList * listAddr,gint addrCount)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  */
mutt_build_items(MuttFile * muttFile,AddressCache * cache,gchar * line)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 			g_list_free_full( list, g_free );
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 	g_list_free_full( list, g_free );
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  */
mutt_read_file(MuttFile * muttFile,AddressCache * cache)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 			g_slist_free_full( listValue, g_free );
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 	g_slist_free_full( listValue, g_free );
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 */
mutt_import_data(MuttFile * muttFile,AddressCache * cache)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 */
mutt_find_file(void)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 
567