/* 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] [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 #include #include #include #include #include #include #include #include #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] [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;itm_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: * <4 char mesg num,right aligned><25 char sender> *