/* The body of the mail message class This is a logical structure for the raw data of a message. -- threading, status, raw read/write, etc. */ #include #include #include "headers.h" #include "mailmsg.h" #include "my_regex.h" #include "main.h" mbox_data:: mbox_data(IObottle *mailfile) { MailFile = mailfile; head = NULL; tail = NULL; nmessages = 0; #ifdef THREADMAIL showthreads = SHOW_THREADS; #else showthreads = NO_THREADS; #endif aborted = false; saveall = true; } char * mailmsg::boundary[] = { "From", NULL }; /* This is called by a program to create a new list of messages */ mailmsg:: mailmsg(mbox_data *MBox) { /* Set the mailbox data */ mbox = MBox; /* Mark the position of this message in the file */ MailFile = mbox->MailFile; mstart = MailFile->tellg(); body = NULL; /* Parse the header and generate a checksum */ static const char *fields[] = { "Date", "Sender", "From", "Reply-To", "To", "Cc", "Subject", "Apparently-To", "Message-Id", "In-Reply-To", "Importance", "Status", "Mime-Version", NULL }; MD5_CTX md5_ctx; unsigned char md5sum[16]; unsigned int i, len; char line[BUFSIZ]; char *data, *ptr; char lastfield[1024], *lastdata=NULL; MD5Init(&md5_ctx); do { len = MailFile->readline(line, BUFSIZ); if ( strncasecmp(line, "Status: ", 8) != 0 ) { MD5Update(&md5_ctx, (unsigned char *)line, len); } /* If the header line starts with whitespace, it continues the last field. */ if ( isspace(line[0]) && lastdata ) { /* Create new appended field data */ for ( ptr=line; isspace(*ptr); ++ptr ); data = new char [strlen(lastdata)+1+strlen(ptr)+1]; sprintf(data, "%s %s", lastdata, ptr); delete[] lastdata; lastdata = data; } else { /* Flush out any previous header field */ if ( lastdata ) { data = MIME_body::DecodeLine(lastdata); fieldtab.Add(lastfield, data); delete[] lastdata; } /* Insert it into the hash list */ /* If the header line contains ": " it is hashable */ lastdata = NULL; if ( ((ptr=strstr(line, ":")) != NULL) && isspace(*(ptr+1)) ) { *ptr = '\0'; bool saveField = false; if ( strncasecmp(line, "Content-", 8) == 0 || strncasecmp(line, "X-", 2) == 0 ) { saveField = true; } else { for ( i = 0; fields[i]; ++i ) { if ( strcasecmp(line, fields[i]) == 0 ) { saveField = true; break; } } } if ( saveField && GetField(line) == NULL ) { for ( ptr += 2; isspace(*ptr); ++ptr ); lastdata = new char[strlen(ptr)+1]; strcpy(lastdata, ptr); strcpy(lastfield, line); } } } } while ( ! MailFile->eof() && (line[0] != '\0') ); /* Flush out any previous header field */ if ( lastdata ) { /* Translate inline QP characters */ data = MIME_body::DecodeLine(lastdata); fieldtab.Add(lastfield, data); delete[] lastdata; } MD5Final(md5sum, &md5_ctx); for ( i = 0; i < sizeof(md5sum); ++i ) { for ( int nibble = 0; nibble < 2; ++nibble ) { int hex; if ( nibble == 0 ) { hex = (md5sum[i] & 0xF0) >> 4; } else { hex = md5sum[i] & 0x0F; } if ( hex > 9 ) { checksum[i*2+nibble] = 'A' + hex; } else { checksum[i*2+nibble] = '0' + hex; } } } checksum[i*2] = '\0'; /* Skip to the next message */ long here; for ( ; ; ) { here = MailFile->tellg(); MailFile->readline(line, BUFSIZ); if ( MailFile->eof() ) break; if ( strncmp(line, "From ", 5) == 0 ) { MailFile->seekg(here); break; } } mlength = (here-mstart); threaded_to = -1; FinishInit(); } mailmsg:: mailmsg(mbox_data *MBox, FILE *cache) { long here; char line[BUFSIZ]; char *ptr; /* Set the mailbox data */ mbox = MBox; /* Read the cached header information */ MailFile = mbox->MailFile; mstart = 0; body = NULL; mlength = 0; fgets(line, BUFSIZ, cache); sscanf(line, "%s %ld %ld %d", checksum, &mstart, &mlength, &threaded_to); for ( ; ; ) { here = ftell(cache); if ( ! fgets(line, BUFSIZ, cache) ) { break; } if ( strcmp(line, CACHED_HEADER"\n") == 0 ) { fseek(cache, here, SEEK_SET); break; } line[strlen(line)-1] = '\0'; /* If the header line contains ": " it is hashable */ if ( ((ptr=strstr(line, ":")) != NULL) && isspace(*(ptr+1)) ) { *ptr = '\0'; ptr += 2; if ( GetField(line) == NULL ) { char *data = new char[strlen(ptr)+1]; strcpy(data, ptr); fieldtab.Add(line, data); } } } FinishInit(); } void mailmsg:: FinishInit() { /* Set up the message list */ privprev = prev = mbox->tail; privnext = next = NULL; if ( !mbox->head ) { mbox->head = this; } mbox->tail = this; index = ++mbox->nmessages; messageID = GetField("Message-Id"); if ( !messageID ) { messageID = checksum; } thread_prev = NULL; thread_next = NULL; threaded = 0; thread_size = 0; thread_key = GetField("In-Reply-To"); const char *field = GetField("Importance"); if ( field && strcasecmp(field, "high") == 0 ) { importance = '*'; } else { importance = ' '; } author = NULL; recipient = NULL; subject_key = NormalizeSubject(GetField("Subject")); subject_hash = HashString(subject_key); status = NULL; Status((char *)GetField("Status")); NoChange(); /* Make sure we delete messages that were aborted previously */ if ( *status == 'D' ) NewField("Status", "O"); if ( privprev ) { privprev->Add(this); } } void mailmsg:: Add(mailmsg *msg) { privnext = next = msg; } int mailmsg:: Save(char *filename) { FILE *output; char buffer[BUFSIZ]; unsigned int len, lenleft; /* Make sure we have somewhere to save. :) */ if ( ! filename || ! *filename ) { errno = EINVAL; return(-1); } /* Open the output file */ if ( (output=fopen(filename, "a")) == NULL ) return(-1); /* Seek appropriately and save out the message */ MailFile->seekg(mstart); for ( lenleft = mlength; lenleft > 0; lenleft -= len ) { if ( lenleft < BUFSIZ ) len = MailFile->read(buffer, lenleft); else len = MailFile->read(buffer, BUFSIZ); if ( len == 0 ) { /* Some read error */ fclose(output); return(-1); } if ( fwrite(buffer, 1, len, output) != len ) { fclose(output); return(-1); } } fclose(output); return(0); } int mailmsg:: SaveStatus() { int retval = -1; long here; long oldpos; char line[BUFSIZ]; oldpos = MailFile->tellg(); MailFile->seekg(mstart); do { here = MailFile->tellg(); MailFile->readline(line, BUFSIZ); if ( strncasecmp(line, "Status: ", 8) == 0 ) { const char *schar; long writepos = MailFile->tellp(); MailFile->seekp(here+8); schar = ((*status == ' ') ? "R" : status); NewField("Status", schar); MailFile->write(schar, 1); MailFile->seekp(writepos); retval = 0; break; } } while ( ! MailFile->eof() && (line[0] != '\0') ); MailFile->seekg(oldpos); return(retval); } int mailmsg:: SaveToDisk(Fifo *ringbuf, int &gap) { long length = 0; char newdata[BUFSIZ]; int newlen, lenleft; int status_written = 0; /* Do it! */ switch (*status) { case 'D': /* We don't save anything to disk */ if ( ! mbox->aborted ) { gap -= mlength; MailFile->seekg(mstart+mlength); /* Force our status to "deleted" */ NewField("Status", "D"); break; } /* If aborted, fall through and save ourselves */ default: /* Save ourselves to disk */ /* First the header */ while ( ! MailFile->fail() ) { newlen = MailFile->readline(newdata, BUFSIZ); gap -= newlen; /* Break at end of header */ if ( newdata[0] == '\0' ) break; /* Check for status line and update it */ /* Hack hack hack. :) */ if ( strncasecmp(newdata, "Status: ", strlen("Status: ")) == 0 ) { const char *schar; char *sptr=newdata+strlen("Status: "); /* There is a status, right? */ if ( ! *sptr ) { continue; } schar = ((*status == ' ') ? "R" : status); *sptr = *schar; NewField("Status", schar); status_written = 1; } /* Queue current data */ QueueRing(ringbuf, newdata, newlen); length += newlen; } /* Now write the current status, if necessary */ if ( ! status_written ) { const char *schar; char sptr[10+1]; schar = ((*status == ' ') ? "R" : status); sprintf(sptr, "Status: %c\n", *schar); NewField("Status", schar); QueueRing(ringbuf, sptr, 10); length += 10; } QueueRing(ringbuf, "\n", 1); length += 1; /* Splash past the body */ lenleft = (mlength-(MailFile->tellg()-mstart)); while ( (lenleft > 0) && ! MailFile->fail() ) { newlen = MailFile->read(newdata, (lenleft > BUFSIZ) ? BUFSIZ : lenleft); /* Queue current data */ QueueRing(ringbuf, newdata, newlen); gap -= newlen; lenleft -= newlen; length += newlen; } /* Simple corruption check -- early end? */ if ( ! MailFile->fail() ) { long here = MailFile->tellg(); newlen = MailFile->read(newdata, 5); if ( newlen > 0 && (newlen < 5 || strncmp(newdata, "From ", 5) != 0) ) { std::cerr << "Warning: Corrupt mailfile?" << std::endl; } MailFile->seekg(here); } break; } return(length); } int mailmsg:: SaveToCache(FILE *cache) { fprintf(cache, "%s\n", CACHED_HEADER); fprintf(cache, "%s %ld %ld %d\n", checksum, mstart, mlength, thread_prev ? thread_prev->index : 0); char *key; char **dataptr; fieldtab.InitIterator(); while ( (dataptr=fieldtab.Iterate(&key)) ) { bool savefield = false; if ( strncasecmp(key, "Content-", 8) != 0 && strncasecmp(key, "X-", 2) != 0 ) { savefield = true; } else if ( strcasecmp(key, "X-Apparently-To") == 0 ) { savefield = true; } if ( savefield ) { fprintf(cache, "%s: %s\n", key, *dataptr); } } return(0); } mailmsg:: ~mailmsg() { delete[] status; if ( author ) delete[] author; if ( recipient ) delete[] recipient; if ( subject_key ) delete[] subject_key; char **dataptr; fieldtab.InitIterator(); while ( (dataptr=fieldtab.Iterate()) ) delete[] *dataptr; if ( body ) delete body; } /* Get the body of the message */ MIME_body * mailmsg:: Body(void) { if(!body) { long oldpos = MailFile->tellg(); MailFile->seekg(mstart); body = new MIME_body(MailFile, boundary); MailFile->seekg(oldpos); } return(body); } /* Verify that our content has not changed. We do this by verifying that our message header hasn't changed. FIXME */ bool mailmsg:: Verify(void) { long oldpos; MD5_CTX md5_ctx; unsigned char md5sum[16]; unsigned int i, len; char line[BUFSIZ]; char newchecksum[33]; MD5Init(&md5_ctx); oldpos = MailFile->tellg(); MailFile->seekg(mstart); do { len = MailFile->readline(line, BUFSIZ); if ( strncasecmp(line, "Status: ", 8) != 0 ) { MD5Update(&md5_ctx, (unsigned char *)line, len); } } while ( ! MailFile->eof() && (line[0] != '\0') ); MailFile->seekg(oldpos); MD5Final(md5sum, &md5_ctx); for ( i = 0; i < sizeof(md5sum); ++i ) { for ( int nibble = 0; nibble < 2; ++nibble ) { int hex; if ( nibble == 0 ) { hex = (md5sum[i] & 0xF0) >> 4; } else { hex = md5sum[i] & 0x0F; } if ( hex > 9 ) { newchecksum[i*2+nibble] = 'A' + hex; } else { newchecksum[i*2+nibble] = '0' + hex; } } } newchecksum[i*2] = '\0'; return (strcmp(checksum, newchecksum) == 0); } void mailmsg:: ThreadPass1() { mailmsg *mesgptr; if ( threaded_to >= 0 ) { if ( threaded_to == 0 ) { return; } if ( threaded_to > index ) { for ( mesgptr = RawNext(); mesgptr; mesgptr = mesgptr->RawNext() ) { if ( threaded_to == mesgptr->index ) { mesgptr->ThreadMe(this); return; } } } else { for ( mesgptr = RawPrev(); mesgptr; mesgptr = mesgptr->RawPrev() ) { if ( threaded_to == mesgptr->index ) { mesgptr->ThreadMe(this); return; } } } /* Couldn't find threaded index?? */ threaded_to = -1; } if ( ! thread_key ) { return; } /* Search backwards through the messages for ID */ for ( mesgptr = RawPrev(); mesgptr; mesgptr = mesgptr->RawPrev() ) { if ( strcmp(thread_key, mesgptr->MessageID()) == 0 || (mesgptr->thread_key && strcmp(thread_key, mesgptr->thread_key) == 0)) { mesgptr->ThreadMe(this); return; } } /* Search forwards through the messages for ID */ for ( mesgptr = RawNext(); mesgptr; mesgptr = mesgptr->RawNext() ) { if ( strcmp(thread_key, mesgptr->MessageID()) == 0 ) { mesgptr->ThreadMe(this); return; } } } void mailmsg:: ThreadPass2() { mailmsg *mesgptr; if ( thread_prev || ! subject_key || threaded_to >= 0 ) { return; } for ( mesgptr = RawPrev(); mesgptr; mesgptr = mesgptr->RawPrev() ) { if ( subject_hash == mesgptr->subject_hash && mesgptr->subject_key && strcmp(subject_key, mesgptr->subject_key) == 0 && !mesgptr->ThreadedOn(this) ) { mesgptr->ThreadMe(this); return; } } } int mailmsg:: ThreadedOn(mailmsg *msg) { mailmsg *mesgptr; for ( mesgptr = thread_prev; mesgptr; mesgptr = mesgptr->thread_prev ) { if ( mesgptr == msg ) return 1; } return 0; } void mailmsg:: ThreadMe(mailmsg *mesg) { mailmsg *mesgptr; int n; /* Stuff ourselves in as a thread */ for ( mesgptr = mesg; mesgptr->thread_next; mesgptr = mesgptr->thread_next ) ; mesgptr->thread_next = thread_next; if ( thread_next ) { thread_next->thread_prev = mesgptr; } thread_next = mesg; mesg->thread_prev = this; /* thread_size (ThreadSize) goes: 3 2 1 (go up) */ for ( mesgptr = mesg; mesgptr->thread_next; mesgptr = mesgptr->thread_next ) ; for ( n=1; mesgptr; mesgptr=mesgptr->thread_prev, ++n ) mesgptr->thread_size = n; /* threaded (ThreadIndex) goes: 0 1 2 (go deeper) */ for ( n=threaded+1, mesgptr = mesg; mesgptr; mesgptr=mesgptr->thread_next, ++n ) mesgptr->threaded = n; } /* See/Set the status of the message */ const char * mailmsg:: Status(bool in_listing) { /* Thread aware. :) */ if ( in_listing && (mbox->showthreads == HIDE_THREADS) ) { /* Return the _thread_ status */ if ( ThreadSize() && ! ThreadIndex() ) { mailmsg *mesgptr; /* Look for any new message */ mesgptr = this; do { if ( *(mesgptr->Status()) == 'N' ) return(mesgptr->Status()); } while ( (mesgptr = mesgptr->ThreadNext()) != NULL ); /* Look for any non-deleted messages */ mesgptr = this; do { if ( *(mesgptr->Status()) != 'D' ) return(mesgptr->Status()); } while ( (mesgptr = mesgptr->ThreadNext()) != NULL ); return("D"); /* Deleted thread */ } } return(status); } const char * mailmsg:: DiskStatus(void) { return(TranslateStatus((char *)GetField("Status"))); } /* Message state transitions and actions */ #define STATE_NULL 0 #define STATE_PUSH 1 #define STATE_POP 2 #define STATE_REPL 3 inline int StateIndex(char State) { switch (State) { case 'N': /* New -- Unread, unreplied */ return(0); case ' ': /* Read and/or replied to */ return(1); case 'D': /* Deleted */ return(2); case 'U': /* Undeleted/Unread */ return(3); default: /* Unknown state */ return(4); } } static int State_Transitions[5][5] = { /* 0 */ /* 1 */ /* 2 */ /* 3 */ /* 4 */ /* 0 */ { STATE_NULL, STATE_PUSH, STATE_PUSH, STATE_NULL, STATE_NULL }, /* 1 */ { STATE_REPL, STATE_NULL, STATE_PUSH, STATE_POP, STATE_NULL }, /* 2 */ { STATE_REPL, STATE_NULL, STATE_NULL, STATE_POP, STATE_NULL }, /* 3 */ { STATE_NULL, STATE_NULL, STATE_NULL, STATE_NULL, STATE_NULL }, /* 4 */ { STATE_REPL, STATE_REPL, STATE_REPL, STATE_NULL, STATE_NULL }, }; void mailmsg:: Status(const char *newstatus) { char *ptr; newstatus = TranslateStatus(newstatus); /* Set default status */ if ( ! status ) { status = new char[2]; strcpy(status, "?"); } /* If no status change, don't mark the status as being changed */ if ( *newstatus == *status ) return; /* Perform the state transition and mark the message as changed */ switch (State_Transitions[StateIndex(*status)][StateIndex(*newstatus)]){ case STATE_NULL: break; case STATE_PUSH: ptr = status; status = new char[1+strlen(ptr)+1]; sprintf(status, "%c%s", *newstatus, ptr); delete[] ptr; break; case STATE_POP: if ( strlen(status) == 1 ) { /* A pop of a single state results in 'N' */ *status = 'N'; } else { ptr = status; status = new char[strlen(ptr)-1+1]; strcpy(status, ptr+1); delete[] ptr; } break; case STATE_REPL: *status = *newstatus; break; } ++changed; return; } /* Thread navigation stuff */ mailmsg * mailmsg:: ByIndex(int the_index) { if ( the_index == index ) return(this); if ( next && (the_index > index) ) return(next->ByIndex(the_index)); if ( prev && (the_index < index) ) return(prev->ByIndex(the_index)); /* Always returns some message */ return(this); } mailmsg * mailmsg:: ByStatus(const char *the_status) { mailmsg *next_one; if ( *the_status == *Status() ) return(this); if ( (next_one=Next()) ) return(next_one->ByStatus(the_status)); return(NULL); } mailmsg * mailmsg:: ByMessageID(const char *messageID) { mailmsg *next_one; if ( strcmp(messageID, MessageID()) == 0 ) return(this); if ( (next_one=Next()) ) return(next_one->ByMessageID(messageID)); return(NULL); } int mailmsg:: ThreadSize(void) { if ( mbox->showthreads == NO_THREADS ) return(0); return(thread_size); } int mailmsg:: ThreadIndex(void) { if ( mbox->showthreads == NO_THREADS ) return(0); return(threaded); } mailmsg * mailmsg:: Next(void) { switch (mbox->showthreads) { case NO_THREADS: return(next); case HIDE_THREADS: { mailmsg *ptr = next; while ( ptr && ptr->threaded ) ptr = ptr->next; return(ptr); } case SHOW_THREADS: if ( ! thread_next ) { mailmsg *ptr; /* Back up the current thread */ ptr = this; while ( ptr->threaded ) ptr = ptr->thread_prev; /* Skip intervening threaded mail */ ptr = ptr->next; while ( ptr && ptr->threaded ) ptr = ptr->next; return(ptr); } else return(thread_next); } return(NULL); /* Error? */ } mailmsg * mailmsg:: Next(int howmany) { mailmsg *mesgptr, *nextptr; for ( mesgptr=this; howmany && (nextptr=mesgptr->Next()); --howmany ) mesgptr = nextptr; return(mesgptr); } mailmsg * mailmsg:: Prev(void) { switch (mbox->showthreads) { case NO_THREADS: return(prev); case HIDE_THREADS: if ( prev && prev->threaded ) return(prev->Prev()); return(prev); case SHOW_THREADS: if ( ! thread_prev ) { mailmsg *ptr = prev; /* Skip past intervening thread msgs */ while ( ptr && ptr->threaded ) ptr = ptr->prev; /* Go to end of possible thread */ while ( ptr && ptr->thread_next ) ptr = ptr->thread_next; return(ptr); } else return(thread_prev); } return(NULL); /* Error? */ } mailmsg * mailmsg:: Prev(int howmany) { mailmsg *mesgptr, *prevptr; for ( mesgptr=this; howmany && (prevptr=mesgptr->Prev()); --howmany ) mesgptr = prevptr; return(mesgptr); } /* Move around, finding an undeleted message */ mailmsg * mailmsg:: SkipDeleted(int *rows) { mailmsg *mesgptr; int i, offset; /* Return ourselves if we qualify. :-) */ if ( *Status() != 'D' ) return(this); if ( ThreadSize() > 0 ) { /* Check for an undeleted message further down */ mesgptr = Next(); offset = 0; for ( i=(ThreadSize()-1); i; --i ) { ++offset; if ( *mesgptr->Status() != 'D' ) { if ( rows ) *rows = offset; return(mesgptr); } mesgptr = mesgptr->Next(); } /* Okay, try further up */ mesgptr = Prev(); offset = 0; for ( i=ThreadIndex(); i; --i ) { --offset; if ( *mesgptr->Status() != 'D' ) { if ( rows ) *rows = offset; return(mesgptr); } mesgptr = mesgptr->Prev(); } } /* Nope, either we are not a thread, or the entire thread is deleted */ mesgptr = Next(); offset = 0; while ( mesgptr ) { ++offset; if ( *mesgptr->Status() != 'D' ) { if ( rows ) *rows = offset; return(mesgptr); } mesgptr = mesgptr->Next(); } return(NULL); } /* Header field stuff */ char * mailmsg:: GetHeader(char **keyholder) { char **value; if ( keyholder == NULL ) { fieldtab.InitIterator(); return(NULL); } if ( (value=fieldtab.Iterate(keyholder)) == NULL ) return(NULL); return(*value); } const char * mailmsg:: GetField(const char *key) { char **dataptr; if ( (dataptr=fieldtab.Search(key)) ) return(*dataptr); return(NULL); } void mailmsg:: NewField(const char *name, const char *value) { char *sptr; /* Remove any existing value */ if ((sptr=(char *)GetField(name))) { delete[] sptr; fieldtab.Remove(name); } sptr = new char[strlen(value)+1]; strcpy(sptr, value); fieldtab.Add(name, sptr); } /* This function takes a mail subject, and strips all "Re:" type strings. */ /* This is useful for determining whether a message is part of a thread */ char * mailmsg:: NormalizeSubject(const char *subject) { char *newsubject; const char *REptr; int RElen; /* Check to make sure there is a subject */ if ( subject ) { newsubject = new char[strlen(subject)+1]; strcpy(newsubject, subject); } else return(NULL); while ( (REptr = match_nocase(newsubject, "^[ \t]*Re[^:]*:[ \t]*", &RElen)) ) { strcpy(const_cast(REptr), REptr+RElen); } while ( (REptr = match_nocase(newsubject, "[ \t]Re:", &RElen)) ) { strcpy(const_cast(REptr), REptr+RElen); } return(newsubject); } int mailmsg:: HashString(char *str) { int num = 0; while ( str && *str ) { num += *str; ++str; } return(num); } static char *SimplifyAddress(const char *address) { char *simple; const char *matchptr; int matchlen; /* Make a copy of the from field */ simple = new char[strlen(address)+1]; strcpy(simple, address); /* Check for a name: "homer@simpsons.org "Homer Simpson (tm)"" */ if ( (matchptr=match(simple, "\".*\"", &matchlen)) ) { strncpy(simple, matchptr+1, matchlen-2); simple[matchlen-2] = '\0'; } else /* Check for a name: "homer@simpsons.org (Homer Simpson)" */ if ( (matchptr=match(simple, "(.*)", &matchlen)) ) { strncpy(simple, matchptr+1, matchlen-2); simple[matchlen-2] = '\0'; } else /* Check for an e-mail address: "Homer Simpson " */ /* This pattern is equivalent to "[ \t]+<.*>", but less buggy. ;) */ if ( (matchptr=match(simple, "[ \t][ \t]*<.*>", &matchlen)) ) { simple[matchptr-simple] = '\0'; } /* Strip any quotes */ if ( (matchptr=match(simple, "\".*\"", &matchlen)) ) { strncpy(simple, matchptr+1, matchlen-2); simple[matchlen-2] = '\0'; } /* That should catch most of them.. :-) */ return(simple); } const char * mailmsg:: Author() { if ( !author ) { author = SimplifyAddress(From()); } return(author); } const char * mailmsg:: Recipient() { if ( !recipient ) { recipient = SimplifyAddress(To()); } return(recipient); } char * mailmsg:: MonthDay(void) { static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; char *datefield, *bad_date="??? ??"; char *buf; char *day, *month; int dayi, monthi; int dlen; static char datebuf[7]; /* Actually, if this failed, we could use the first "From ..." line */ if ( (datefield=(char *)GetField("Date")) == NULL ) return(bad_date); else { char *dptr = new char[strlen(datefield)+1]; strcpy(dptr, datefield); datefield = dptr; } /* Extract the portions of the string that we need */ buf = (char *)match(datefield, "[0-9][0-9]* [A-Z][a-z][a-z]", &dlen); if ( buf == NULL ) { delete[] datefield; return(bad_date); } /* Grab the day */ day = buf; dayi = atoi(day); /* Skip to the month */ month = buf; for ( ; !isspace(*month); ++month ); for ( ; isspace(*month); ++month ); /* Get the number of the month */ for ( monthi = 0; months[monthi]; ++monthi ) { if ( strncasecmp(month, months[monthi], 3) == 0 ) break; } if ( ! months[monthi] ) { delete[] datefield; return(bad_date); } /* Put it all together */ sprintf(datebuf, "%s %.2d", months[monthi], dayi); delete[] datefield; return(datebuf); }