|
|
|
|
/*
For information on this software or to report bugs please visit http://www.ranton.org
or email ranton@ranton.org
For custom software development and consulting please visit http://www.madlogic.com .
Copyright (c) 2005, Richard Neal Anton
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
* The name of the copyright holder may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// mboxparse.cpp
// Usage: mboxparse [-s] <filename> [filter]
// if -s option is present as first argument, then only summary
// information about messages(from,subject) is displayed
// otherwise the entire messages are printed.
// Message filtering:
// This accepts filters when selecting messages to view
// which understand * and ? wildcards
// If the filter matches the from or the subject the message
// is displayed.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#define kStringSize 1024
enum {
kMesgNew,
kMesgUnread,
kMesgOld
};
typedef struct MesgStruct {
long offset;
char subject[kStringSize];
char from[kStringSize];
int status;
struct MesgStruct *next;
} MesgRec;
static char *kMonth[13] =
{
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
NULL
};
static char *kDay[8] =
{
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
NULL
};
static int kMonthCount[12] =
{
31,
28, /* now actually dealing w leap year in code*/
31,/* 90 */
30,
31, /* 151 */
30,
31, /* 222 */
31,
30, /* 283 */
31,
30,/* 344 */
31 /* 365 */
};
bool CompareWithFilter(const char *string, const char *filter);
int ReadRecord(
FILE *inFile,
bool *outValid,
char outFrom[],
char outFromLine[],
time_t *outTimeT);
/* in good ANSI fashion this returns -1 if the string
* is not found. */
int FindStrInTable( char **inTable, int inTableSize, char *inStr );
/* calculate yearday from the year,
* the month, and the day of the month
* year is as in 1997, etc.
*/
int CalcYearDay( int year, int month, int day );
int MonthNumFromString( char *inStr );
int DayNumFromString( char *inStr );
/* reads entire message from file */
int ReadMessageText(
FILE *inFile,
MesgRec *inMesgList,
unsigned long inMesgNum,
char **outMesg,
size_t *outMesgSize);
/* utility function to go through mesg list and
* find a mesg by index.
*/
MesgRec *GetMessageFromList(MesgRec *inMesgList, unsigned long inMesgNum);
/* displays a message and sets the
* status to old mail
*/
int DisplayMessage(
FILE *inMailFile,
MesgRec *inMesgList,
unsigned long inCurrent
);
/* this builds the menu item string
* for a message given the message
* record
*/
char *BuildMenuString( MesgRec *inMesg, unsigned long inMesgNum );
void PrintSummaryLines(MesgRec *inMesgList, const char *filter);
void PrintSummaryLine(MesgRec *mesg, unsigned mesgNum);
void PrintMessages(FILE *inMailFile, MesgRec *inMesgList, const char *filter);
static void Usage(void)
{
fprintf(stderr,"Usage: mboxparse [-s] <filename> [filter]\n");
}
int main(int argc, char **argv)
{
const char *mailFileName = NULL;
const char *filter = "*";
bool valid;
FILE *mailFile=(FILE *)NULL;
time_t mesgTime;
MesgRec *mesgList = (MesgRec *)NULL, *mesg = (MesgRec *)NULL;
MesgRec *swap;
long offset;
char line[kStringSize];
bool foundSubject, foundStatus, foundFrom;
char str[kStringSize]; /* this is a generic temp string variable */
char
from[kStringSize],
// subject[kStringSize],
fromLine[kStringSize];
long mesgCount = 0;
long sofar = 0;
long stop;
// bool found;
bool summary = false;
if( argc < 2 || argc > 4 )
{
Usage();
return 2;
}
int args = argc - 1;
char **argp = argv+1;
if( !strcmp("-s",*argp) )
{
summary = true;
args--;
argp++;
}
if( !args )
{
Usage();
return 2;
}
else
{
mailFileName = *argp;
args--;
argp++;
}
if( args )
{
filter = *argp;
args--;
argp++;
}
mailFile = fopen(mailFileName,"rb");
if( !mailFile )
{
printf("Unable to open input file.\n");
return 1;
}
sofar = 0;
while(!feof( mailFile ) )
{
offset = ftell( mailFile );
if( offset == -1 )
{
fprintf(stderr,"Error getting file position.\n");
return 1;
}
if( ReadRecord(
mailFile,
&valid,
from,
fromLine,
&mesgTime ) )
{
if( valid )
{
/*
* put it into the mesg list
*/
mesg = (MesgRec *)malloc( sizeof( MesgRec ) );
if( !mesg )
{
fprintf( stderr, "Out of memory!\n");
return 1;
}
mesg->offset = offset;
strncpy( mesg->from, from, kStringSize );
mesg->next = mesgList;
mesgList = mesg;
// print it out as we go.
// printf("%u: %s", sofar++, fromLine);
}
}
}
/* need to put the list in forward order */
swap = (MesgRec *)NULL;
while(mesgList)
{
mesg = mesgList;
mesgList = mesgList->next;
mesg->next = swap;
swap = mesg;
}
mesgList = swap;
/* now go back through list,
* and read other information out
* of the messages */
mesg = mesgList;
while( mesg )
{
if( mesg->next )
stop = mesg->next->offset;
else
{
fseek( mailFile,0, SEEK_END );
stop = ftell( mailFile );
}
/* go to the appropriate file positon */
offset = mesg->offset;
if( fseek( mailFile, (size_t)offset, SEEK_SET ) )
{
fprintf(stderr,"Error setting position in mailfile.\n");
return 1;
}
mesg->subject[0] = '\0';
/* find the subject */
/* and then the status */
foundFrom = false;
foundSubject = false;
foundStatus = false;
mesg->status = kMesgNew; /* this is the status if no status field is
found so set it here and don't change it
unless we find one */
/* be sure not to go into next message */
while( ftell( mailFile ) < stop )
{
fgets( line, kStringSize, mailFile );
if( ferror( mailFile ) )
{
fprintf( stderr, "Error reading mail file.\n" );
return 1;
}
/* see if we can match this line to anything we understand,
* and if we've found one already ignore it. */
if( !foundSubject && !strncmp( line, "Subject:", 8 ) )
{
strcpy( mesg->subject, &(line[8]) );
/* get rid of the newline */
mesg->subject[ strlen(mesg->subject)-1 ] = '\0';
foundSubject = true;
}
else if( !foundStatus && !strncmp( line, "Status:", 7 ) )
{
strcpy( str, strtok( &(line[7]), " \n" ) );
if( !strcmp( str, "RO" ) )
mesg->status = kMesgOld;
else if( !strcmp( str, "O" ) )
mesg->status = kMesgUnread;
else
mesg->status = kMesgOld; /* guess old */
foundStatus = true;
}
else if( !foundFrom && !strncmp( line, "From:", 5 ) )
{
strcpy( mesg->from, &(line[5]) );
mesg->from[ strlen(mesg->from)-1 ] = '\0';
foundFrom = true;
}
}
mesg = mesg->next;
}
/* now we have our message information,
* so build the menu
*/
mesgCount = 0;
mesg = mesgList;
while( mesg )
{
mesgCount++;
mesg = mesg->next;
}
if( !mesgCount )
{
printf( "No messages.\n");
return 0;
}
// filter is checked against from and subject, and if either matches
// message is displayed.
// now print out summary information.
if( summary )
PrintSummaryLines(mesgList,filter);
else
PrintMessages(mailFile,mesgList,filter);
fclose( mailFile );
/* free all of our shit */
while( mesgList )
{
mesg = mesgList;
mesgList = mesgList->next;
free( (void *)mesg );
}
return 0;
}
void PrintSummaryLines(MesgRec *inMesgList, const char *filter)
{
unsigned mesgCount = 0;
MesgRec *mesg = inMesgList;
while( mesg )
{
// if it matches filter then display it.
if( CompareWithFilter(mesg->from,filter) ||
CompareWithFilter(mesg->subject,filter) )
{
PrintSummaryLine(mesg,mesgCount);
mesgCount++;
}
mesg = mesg->next;
}
}
void PrintSummaryLine(MesgRec *mesg, unsigned mesgNum)
{
char *line = BuildMenuString( mesg, mesgNum );
if( line )
{
printf("%s\n",line);
free((void*)line);
}
}
void PrintMessages(FILE *inMailFile, MesgRec *inMesgList, const char *filter)
{
unsigned mesgCount = 0;
MesgRec *mesg = inMesgList;
while( mesg )
{
// if it matches filter then display it.
if( CompareWithFilter(mesg->from,filter) ||
CompareWithFilter(mesg->subject,filter) )
{
// PrintSummaryLine(mesg,mesgCount);
DisplayMessage(inMailFile,inMesgList,mesgCount);
mesgCount++;
}
mesg = mesg->next;
}
}
/* in good ANSI fashion this returns -1 if the string
* is not found. */
int FindStrInTable( char **inTable, int inTableSize, char *inStr )
{
int i;
for(i = 0;i<inTableSize;i++)
{
if( !strcmp( inTable[i], inStr ) )
return i;
}
return -1;
}
/* calculate yearday from the year,
* the month, and the day of the month
* year is as in 1997, etc.
*/
int CalcYearDay( int year, int month, int day )
{
int yDay;
int count;
for(count=0;count<=month;count++)
{
yDay += kMonthCount[count];
if( count == 1 && /* february */
(year % 4 == 0 || /* check for leap year */
year % 100 == 0 ||
year % 400 == 0 ) )
yDay ++;
}
yDay += day - 1;
return yDay;
}
int MonthNumFromString( char *inStr )
{
return FindStrInTable( kMonth, 12, inStr );
}
/* get day num from string
* returns -1 if not found */
int DayNumFromString( char *inStr )
{
return FindStrInTable( kDay, 7, inStr );
}
int ReadRecord(
FILE *inFile,
bool *outValid,
char outFrom[],
char outFromLine[],
time_t *outTimeT)
{
char
line[kStringSize],
day[kStringSize],
from[kStringSize],
month[kStringSize],
timeTemp[kStringSize],
unitTemp[3];
int temp;
struct tm mesgTime, *outTime;
*outValid = false;
line[0] = '\0';
/* First off get the whole line at once, because
* we're doing this a line at a time and we don't
* want to read more or less than a line at a time
*/
fgets( line, kStringSize, inFile );
if( strncmp( line, "From ", 5) )
return 1;
if( ferror(inFile) || feof(inFile) )
{
if( feof(inFile ) )
return 1;
else
{
fprintf(stderr,"Error reading mail folder.\n");
return 0;
}
}
/* What the fuck??? -RNA */
outTime = &mesgTime;
/* the scanf from hell.*/
sscanf(line,"%s %s %s %s %d %s %d",
from,outFrom,day,month, &(outTime->tm_mday),
timeTemp, &(outTime->tm_year) );
unitTemp[2] = '\0';
unitTemp[0] = timeTemp[0];
unitTemp[1] = timeTemp[1];
if( !isdigit(unitTemp[0]) || !isdigit(unitTemp[1] ) )
return 1;
outTime->tm_hour = atoi( unitTemp );
unitTemp[0] = timeTemp[3];
unitTemp[1] = timeTemp[4];
if( !isdigit(unitTemp[0]) || !isdigit(unitTemp[1] ) )
return 1;
outTime->tm_min = atoi( unitTemp );
unitTemp[0] = timeTemp[6];
unitTemp[1] = timeTemp[7];
if( !isdigit(unitTemp[0]) || !isdigit(unitTemp[1] ) )
return 1;
outTime->tm_sec = atoi( unitTemp );
/* now for the month */
temp = MonthNumFromString(month);
if( temp == -1 )
return 1;
outTime->tm_mon = temp;
/* now for the day of the week */
temp = DayNumFromString( day );
if( temp == -1 )
return 1;
outTime->tm_wday = temp;
/* now for year day */
outTime->tm_yday = CalcYearDay( outTime->tm_year, outTime->tm_mon, outTime->tm_mday );
/* NOTE: I'm not sure but in the following code I beleive we
* have what is called a total failure to deal with
* time zones. -RNA
*/
/* now that we have the year day out of the way,
* subtract 1900 from the year so that it is
* in the proper format.
*/
outTime->tm_year -= 1900;
/* set the daylight savings time,
* and we should be able to call
* mktime
*/
outTime->tm_isdst = daylight; /* not dealing with when we cross it */
*outTimeT = mktime( outTime );
if( *outTimeT == -1 )
{
fprintf(stderr,"Error computing time.\n");
}
*outValid = true;
strcpy(outFromLine,line);
return 1;
}
/* reads entire message from file */
int ReadMessageText(
FILE *inFile,
MesgRec *inMesgList,
unsigned long inMesgNum,
char **outMesg,
size_t *outMesgSize)
{
long start, stop;
MesgRec *mesg;
mesg = inMesgList;
while( mesg && inMesgNum-- )
mesg = mesg->next;
if( !mesg )
{
fprintf( stderr, "Internal error with message list!\n" );
return 1;
}
start = mesg->offset;
if( mesg->next )
stop = mesg->next->offset;
else
{
if( fseek( inFile, 0, SEEK_END ) )
{
fprintf( stderr, "Error from fseek in ReadMessageText.\n");
return 1;
}
stop = ftell( inFile );
if( stop < 0 )
{
fprintf( stderr, "Error from ftell in ReadMessageText.\n");
return 1;
}
}
if( stop <= start )
{
fprintf( stderr, "Error message of zero or less length. \n");
return 1;
}
/* now that we have determined the start and stop, without
* something getting fubar'ed, read the bitch in after
* allocating some handy-dandy Memory(tm)
*/
if( !outMesg )
{
fprintf( stderr, "Null ptr in ReadMessageText.\n");
return 1;
}
*outMesg = (char *)malloc( sizeof(char) * (size_t)(stop-start) );
if( !(*outMesg) )
{
fprintf( stderr, "Out of memory!\n");
return 1;
}
if( fseek( inFile, (long)start, SEEK_SET ) )
{
fprintf( stderr, "Error seeking in mailfile.\n" );
return 1;
}
/* I think since we already dealt with end of file bullshit
* we can safely ignore the return for bytes actually read
* bullshit..
*/
(void)fread( (void *)*outMesg, (size_t)(stop-start), 1, inFile );
if( ferror(inFile) )
{
fprintf( stderr, "Error reading message data.\n");
return 1;
}
*outMesgSize = (size_t)(stop-start);
/* all done */
return 0;
}
/* displays a message and sets the
* status to old mail
*/
int DisplayMessage(
FILE *inMailFile,
MesgRec *inMesgList,
unsigned long inCurrent
)
{
MesgRec *mesg;
char *mesgDataPtr = NULL;
FILE *pagerPipe = NULL;
size_t mesgSize;
/* doubt this is the proper type name to use. */
/* display the file. */
/* move cursor */
/* copy it to a temp file *
* the pass it to the pager program *
* when it returns we need to redraw,
*/
if( ReadMessageText(
inMailFile,
inMesgList,
inCurrent,
&mesgDataPtr,
&mesgSize) )
{
fprintf( stderr, "Error reading message.\n" );
return -1;
}
// write it to stdout.
if( fwrite( (void *)mesgDataPtr,1, mesgSize, stdout ) != mesgSize )
{
fprintf( stderr, "Error writing to pipe.\n" );
free( (void *)mesgDataPtr );
return -1;
}
free( (void *)mesgDataPtr );
mesgDataPtr = NULL;
/* change message status */
mesg = GetMessageFromList(inMesgList,inCurrent);
mesg->status = kMesgOld;
return 0;
}
/* utility function to go through mesg list and
* find a mesg by index.
*/
MesgRec *GetMessageFromList(MesgRec *inMesgList, unsigned long inMesgNum)
{
while( inMesgNum -- )
{
assert(inMesgList);
inMesgList = inMesgList->next;
}
assert( inMesgList );
return inMesgList;
}
/* this builds the menu item string
* for a message given the message
* record
*/
char *BuildMenuString( MesgRec *inMesg, unsigned long inMesgNum )
{
/* ok, display format is:
* <status><4 char mesg num,right aligned><sp><25 char sender>
* <sp><sp><weekday><sp><month><sp><day><sp><time><sp><sp><subject>
*/
/* so thats
* 1 for status
* 4 for mesg number
* 1 for space
* 25 for sender
* 2 for spaces
* 3 for weekday
* 1 for space
* 3 for month
* 1 for space
* 2 for day
* 1 for space
* 5 for time
* 2 for spaces
* 51 so far?
* rest for subject
*/
char str[kStringSize];
char *ret = NULL;
// stupid windows underscore version of snprintf,
#if defined(WIN32)
_snprintf
#else
snprintf
#endif
(
str, kStringSize, " %4lu %.40s %s",
(inMesgNum+1),
inMesg->from,
inMesg->subject );
if( inMesg->status == kMesgNew )
str[0] = 'N';
else if( inMesg->status == kMesgUnread )
str[0] = 'U';
ret = strdup( str );
return ret;
}
static unsigned CountSplats(const char *filter)
{
unsigned ret = 0;
while(*filter)
{
if(*filter == '*')
ret++;
filter++;
}
return ret;
}
// compare 2 strings, ignoring case
static bool CompareNoSplats( const char *a, const char *b )
{
int diff = 0;
char ac, bc;
// cpAssert( a && b );
for(;;)
{
ac = *a;
if( ac >= 'a' && ac <= 'z' )
ac += 'A' - 'a';
bc = *b;
if( bc >= 'a' && bc <= 'z' )
bc += 'A' - 'a';
diff = ac - bc;
if( (diff || !ac || !bc) && (ac != '?') && (bc != '?') ) break;
a++;
b++;
}
return (diff == 0);
}
static bool CompareLetter( char ac, char bc)
{
if( ac >= 'a' && ac <= 'z' )
ac += 'A' - 'a';
if( bc >= 'a' && bc <= 'z' )
bc += 'A' - 'a';
if( (ac != bc) && (ac != '?') && (bc != '?') )
return false;
else
return true;
}
bool CompareWithFilter(const char *string, const char *filter)
{
// only supporting * and ?
unsigned filterlen = strlen(filter);
unsigned stringlen = strlen(string);
unsigned splatCount = CountSplats(filter);
if( !filterlen) // treat no filter like '*'
return true;
if( (filterlen == 1) && (*filter == '*') )
return true;
if( !stringlen ) // can only match splat which has already been checked for.
return false;
if( !splatCount )
return CompareNoSplats(string, filter);
// proceed to the splat
while(*filter != '*')
{
// if the letters don't match, return false
if(!CompareLetter(*string, *filter))
return false;
// the letters do match, go to the next one
string++;
filter++;
}
// make sure no wacko put two splats together
while( (*filter == '*') && (filter[1] == '*') )
filter++;
// if we got to the end
if( (*filter == '*') && (filter[1] == 0) )
return true;
// now, the splat can 'match' 0 to strlen(string) - 1 characters
stringlen = strlen(string);
for(unsigned i = 0; i < stringlen-1; i++)
{
if(CompareWithFilter(string + i, filter + 1))
return true;
}
return false;
}
|