bincimap

Log | Files | Refs | LICENSE

operator-fetch.cc (18610B)


      1 /* -*- Mode: c++; -*- */
      2 /*  --------------------------------------------------------------------
      3  *  Filename:
      4  *    operator-fetch.cc
      5  *  
      6  *  Description:
      7  *    Implementation of the FETCH command.
      8  *
      9  *  Authors:
     10  *    Andreas Aardal Hanssen <andreas-binc curly bincimap spot org>
     11  *
     12  *  Bugs:
     13  *
     14  *  ChangeLog:
     15  *
     16  *  --------------------------------------------------------------------
     17  *  Copyright 2002-2005 Andreas Aardal Hanssen
     18  *
     19  *  This program is free software; you can redistribute it and/or modify
     20  *  it under the terms of the GNU General Public License as published by
     21  *  the Free Software Foundation; either version 2 of the License, or
     22  *  (at your option) any later version.
     23  *
     24  *  This program is distributed in the hope that it will be useful,
     25  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     26  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     27  *  GNU General Public License for more details.
     28  *
     29  *  You should have received a copy of the GNU General Public License
     30  *  along with this program; if not, write to the Free Software
     31  *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
     32  *  --------------------------------------------------------------------
     33  */
     34 #ifdef HAVE_CONFIG_H
     35 #include <config.h>
     36 #endif
     37 
     38 #include <string>
     39 
     40 #include "depot.h"
     41 #include "io.h"
     42 #include "mailbox.h"
     43 #include "operators.h"
     44 #include "imapparser.h"
     45 #include "pendingupdates.h"
     46 #include "recursivedescent.h"
     47 #include "session.h"
     48 #include "convert.h"
     49 
     50 using namespace ::std;
     51 using namespace Binc;
     52 
     53 namespace {
     54   void outputFlags(const Message & message) 
     55   {
     56     IO &com = IOFactory::getInstance().get(1);
     57 
     58     com << "FLAGS ";
     59 	  
     60     com << "(";
     61     int flags = message.getStdFlags();
     62     vector<string> flagv;
     63     if (flags & Message::F_SEEN) flagv.push_back("\\Seen");
     64     if (flags & Message::F_ANSWERED) flagv.push_back("\\Answered");
     65     if (flags & Message::F_DELETED) flagv.push_back("\\Deleted");
     66     if (flags & Message::F_DRAFT) flagv.push_back("\\Draft");
     67     if (flags & Message::F_RECENT) flagv.push_back("\\Recent");
     68     if (flags & Message::F_FLAGGED) flagv.push_back("\\Flagged");
     69     for (vector<string>::const_iterator k 
     70 	   = flagv.begin(); k != flagv.end(); ++k) {
     71       if (k != flagv.begin()) com << " ";
     72       com << *k;
     73     }
     74     com << ")";
     75   }
     76 
     77 }
     78 
     79 //----------------------------------------------------------------------
     80 FetchOperator::FetchOperator(void)
     81 {
     82 }
     83 
     84 //----------------------------------------------------------------------
     85 FetchOperator::~FetchOperator(void)
     86 {
     87 }
     88 
     89 //----------------------------------------------------------------------
     90 const string FetchOperator::getName(void) const
     91 {
     92   return "FETCH";
     93 }
     94 
     95 //----------------------------------------------------------------------
     96 int FetchOperator::getState(void) const
     97 {
     98   return Session::SELECTED;
     99 }
    100 
    101 //------------------------------------------------------------------------
    102 Operator::ProcessResult FetchOperator::process(Depot &depot,
    103 					       Request &request)
    104 {
    105   IO &com = IOFactory::getInstance().get(1);
    106   Session &session = Session::getInstance();
    107 
    108   bool updateFlags = false;
    109   Request req = request;
    110 
    111   Mailbox *mailbox = depot.getSelected();
    112 
    113   // If this is a UID FETCH, check if the UID attribute is fetched. If
    114   // it is not, then add it to the list of fetch attributes.
    115   vector<BincImapParserFetchAtt>::const_iterator f_i;
    116   bool uidfetched = false;
    117   if (request.getUidMode()) {
    118     f_i = request.fatt.begin();
    119     while (f_i != request.fatt.end()) {
    120       if ((*f_i).type == "UID") {
    121 	uidfetched = true;
    122 	break;
    123       }
    124       
    125       f_i++;
    126     }
    127 
    128     if (!uidfetched) {
    129       BincImapParserFetchAtt b;
    130       b.type = "UID";
    131       req.fatt.push_back(b);
    132     }
    133   }
    134 
    135   // Convert macros ALL, FULL and FAST
    136   f_i = request.fatt.begin();
    137   while (f_i != request.fatt.end()) {
    138     const string &type = (*f_i).type;
    139     if (type == "ALL") {
    140       req.fatt.push_back(BincImapParserFetchAtt("FLAGS"));
    141       req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE"));
    142       req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE"));
    143       req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE"));
    144     } else if (type == "FULL") {
    145       req.fatt.push_back(BincImapParserFetchAtt("FLAGS"));
    146       req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE"));
    147       req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE"));
    148       req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE"));
    149       req.fatt.push_back(BincImapParserFetchAtt("BODY"));
    150     } else if (type == "FAST") {
    151       req.fatt.push_back(BincImapParserFetchAtt("FLAGS"));
    152       req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE"));
    153       req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE"));
    154     }
    155 
    156     ++f_i;
    157   }
    158 
    159   int mode;
    160   if (req.getUidMode())
    161     mode = Mailbox::UID_MODE;
    162   else
    163     mode = Mailbox::SQNR_MODE;
    164 
    165   Mailbox::iterator i
    166     = mailbox->begin(req.bset, Mailbox::SKIP_EXPUNGED | mode);
    167 
    168   for (; i != mailbox->end(); ++i) {
    169     Message &message = *i;
    170     
    171     com << "* " << i.getSqnr() << " FETCH (";
    172     bool hasprinted = false;
    173     f_i = req.fatt.begin();
    174     while (f_i != req.fatt.end()) {
    175       BincImapParserFetchAtt fatt = *f_i;
    176 
    177       string prefix = "";
    178       if (hasprinted)
    179 	prefix = " ";
    180 	
    181       if (fatt.type == "FLAGS") {
    182 	// FLAGS
    183 	hasprinted = true;
    184 	com << prefix;
    185 
    186 	outputFlags(message);
    187       } else if (fatt.type == "UID") {
    188 	// UID
    189 	hasprinted = true;
    190 	com << prefix << "UID " << message.getUID();
    191       } else if (fatt.type == "RFC822.SIZE") {
    192 	// RFC822.SIZE
    193 	hasprinted = true;
    194 	com << prefix << "RFC822.SIZE " << message.getSize(true);
    195       } else if (fatt.type == "ENVELOPE") {
    196 	// ENVELOPE
    197 	hasprinted = true;
    198 	com << prefix << "ENVELOPE ";
    199 	message.printEnvelope();
    200       } else if (fatt.type == "BODYSTRUCTURE") {
    201 	// BODYSTRUCTURE
    202 	hasprinted = true;
    203 	com << prefix << "BODYSTRUCTURE ";
    204 	message.printBodyStructure(true);
    205       } else if (fatt.type == "BODY" && !fatt.hassection) {
    206 	// BODY with no section
    207 	hasprinted = true;
    208 	session.addBody();
    209 	com << prefix << "BODY ";
    210 	message.printBodyStructure(false);
    211       } else if (fatt.type == "INTERNALDATE") {
    212 	// INTERNALDATE
    213 	hasprinted = true;
    214 	com << prefix << "INTERNALDATE ";
    215 
    216 	time_t iDate = message.getInternalDate();
    217 	struct tm *_tm = gmtime(&iDate);
    218 	char internal[64];
    219 	string iDateStr;
    220 	if (strftime(internal, sizeof(internal),
    221 		     "%d-%b-%Y %H:%M:%S %z", _tm) != 0)
    222 	  iDateStr = internal;
    223 	else
    224 	  iDateStr = "NIL";
    225 
    226 	com << toImapString(iDateStr);
    227       } else if (fatt.type == "BODY" || fatt.type == "BODY.PEEK") {
    228 	// BODY & BODY.PEEK
    229 	hasprinted = true;
    230 	session.addBody();
    231 
    232 	com << prefix;
    233 	bool peek = (fatt.type == "BODY.PEEK");
    234 	com << fatt.toString();
    235 
    236 	bool includeheaders = true;
    237 	bool fullheader = false;
    238 	bool bodyfetch = false;
    239 
    240 	if (fatt.section != "" || fatt.sectiontext == ""
    241 	    || fatt.sectiontext == "TEXT") {
    242 	  bodyfetch = true;
    243 	  fullheader = true;
    244 	}
    245 
    246 	if (fatt.sectiontext == "HEADER.FIELDS.NOT")
    247 	  includeheaders = false;
    248 
    249 	if (fatt.sectiontext == "HEADER"
    250 	    || fatt.sectiontext == "HEADER.FIELDS"
    251 	    || fatt.sectiontext == "HEADER.FIELDS.NOT"
    252 	    || fatt.sectiontext == "MIME") {
    253 
    254 	  vector<string> v;
    255 	  
    256 	  if (fatt.sectiontext == "MIME") {
    257 	    v.push_back("content-type");
    258 	    v.push_back("content-transfer-encoding");
    259 	    v.push_back("content-disposition");
    260 	    v.push_back("content-description");
    261 	  } else
    262 	    v = fatt.headerlist;
    263     
    264 	  string dummy;
    265 	  unsigned int size = fullheader
    266 	    ? message.getHeaderSize(fatt.section, v,
    267 				    true, 
    268 				    fatt.offsetstart, 
    269 				    fatt.offsetlength, fatt.sectiontext == "MIME")
    270 	    : message.getHeaderSize(fatt.section, fatt.headerlist,
    271 				    includeheaders, 
    272 				    fatt.offsetstart,
    273 				    fatt.offsetlength, fatt.sectiontext == "MIME");
    274 
    275 	  com << "{" << size << "}\r\n";
    276 	  
    277 	  if (fullheader) {
    278 	    message.printHeader(fatt.section, v,
    279 				true, 
    280 				fatt.offsetstart,
    281 				fatt.offsetlength, fatt.sectiontext == "MIME");
    282 	  } else {
    283 	    message.printHeader(fatt.section, fatt.headerlist,
    284 				includeheaders,
    285 				fatt.offsetstart, 
    286 				fatt.offsetlength, fatt.sectiontext == "MIME");
    287 	  }
    288 	} else {
    289 	  unsigned int size;
    290 	  if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT")
    291 	      && fatt.section == "")
    292 	    size = message.getDocSize(fatt.offsetstart,
    293 				      fatt.offsetlength,
    294 				      fatt.sectiontext == "TEXT");
    295 	  else
    296 	    size = message.getBodySize(fatt.section, 
    297 				       fatt.offsetstart, 
    298 				       fatt.offsetlength);
    299 	  
    300 	  com << "{" << size << "}\r\n";
    301 	  
    302 	  if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT")
    303 	      && fatt.section == "")
    304 	    message.printDoc(fatt.offsetstart, 
    305 			     fatt.offsetlength,
    306 			     fatt.sectiontext == "TEXT");
    307 	  else
    308 	    message.printBody(fatt.section, fatt.offsetstart, 
    309 			      fatt.offsetlength);
    310 	}
    311 
    312 	// set the \Seen flag if .PEEK is not used.
    313 	if (!peek)
    314 	  if ((message.getStdFlags() & Message::F_SEEN) == 0)
    315 	    message.setStdFlag(Message::F_SEEN);
    316 
    317       } else if (fatt.type == "RFC822") {
    318 	com << prefix;
    319 	hasprinted = true;
    320 	session.addBody();
    321 
    322 	com << fatt.toString();
    323 
    324 	unsigned int size = message.getDocSize(fatt.offsetstart, 
    325 					       fatt.offsetlength);
    326 
    327 	com << " {" << size << "}\r\n";
    328 
    329 	message.printDoc(fatt.offsetstart, fatt.offsetlength);
    330 	    
    331 	// set the \Seen flag
    332 	if ((message.getStdFlags() & Message::F_SEEN) == 0)
    333 	  message.setStdFlag(Message::F_SEEN);
    334 
    335       } else if (fatt.type == "RFC822.HEADER") {
    336 	com << prefix;
    337 	hasprinted = true;
    338 
    339 	com << fatt.toString();
    340 
    341 	vector<string> v;
    342 	string dummy;
    343 
    344 	unsigned int size = message.getHeaderSize("", v, true,
    345 						  fatt.offsetstart, 
    346 						  fatt.offsetlength);
    347 
    348 	com << " {" << size << "}\r\n";
    349 
    350 	message.printHeader("", v, true, fatt.offsetstart, 
    351 			    fatt.offsetlength);
    352 	    
    353       } else if (fatt.type == "RFC822.TEXT") {
    354 	// RFC822.TEXT
    355 	com << prefix;
    356 	hasprinted = true;
    357 	session.addBody();
    358 
    359 	com << fatt.toString();
    360 
    361 	bool bodyfetch = false;
    362 	bodyfetch = true;
    363 
    364 	unsigned int size;
    365 	if (fatt.sectiontext == "" && fatt.section == "")
    366 	  size = message.getDocSize(fatt.offsetstart, 
    367 				fatt.offsetlength, true);
    368 	else
    369 	  size = message.getBodySize(fatt.section, fatt.offsetstart, 
    370 				     fatt.offsetlength);
    371 	    
    372 	com << " {" << size << "}\r\n";
    373 	    
    374 	if (fatt.sectiontext == "" && fatt.section == "")
    375 	  message.printDoc(fatt.offsetstart, 
    376 			   fatt.offsetlength, true);
    377 	else
    378 	  message.printBody(fatt.section, fatt.offsetstart, 
    379 			    fatt.offsetlength);
    380 
    381 	// set the \Seen flag
    382 	if ((message.getStdFlags() & Message::F_SEEN) == 0)
    383 	  message.setStdFlag(Message::F_SEEN);
    384       } else {
    385 	// Unrecognized fetch_att, this is stopped by the parser
    386 	// so we never get here.
    387       }
    388 
    389       f_i++;
    390     }
    391 
    392     // FIXME: how are parse error passed back?
    393 
    394     com << ")" << endl;
    395 
    396     if (message.hasFlagsChanged()) {
    397       updateFlags = true;
    398       com << "* " << i.getSqnr() << " FETCH (";
    399       outputFlags(message);
    400       com << ")" << endl;
    401       message.setFlagsUnchanged();
    402     }
    403   }
    404   
    405   if (updateFlags) mailbox->updateFlags();
    406 
    407   if (!pendingUpdates(mailbox,
    408 		      PendingUpdates::FLAGS
    409 		      | PendingUpdates::EXISTS
    410 		      | PendingUpdates::RECENT
    411 		      | PendingUpdates::EXPUNGE,
    412 		      true)) {
    413     IO &logger = IOFactory::getInstance().get(2);
    414     logger << "when scanning mailbox: "
    415 	   << session.getLastError() << endl;
    416     com << "* BYE " << session.getLastError() << endl;
    417     com.flushContent();
    418     session.setState(Session::LOGOUT);
    419     return NOTHING;
    420   }
    421 
    422   return OK;
    423 }
    424 
    425 //----------------------------------------------------------------------
    426 Operator::ParseResult FetchOperator::parse(Request &c_in) const
    427 {
    428   Session &session = Session::getInstance();
    429 
    430   Operator::ParseResult res;
    431   if ((res = expectSPACE()) != ACCEPT) {
    432     session.setLastError("Expected SPACE after FETCH");
    433     return res;
    434   }
    435 
    436   if ((res = expectSet(c_in.getSet())) != ACCEPT) {
    437     session.setLastError("Expected sequence set after FETCH SPACE");
    438     return res;
    439   }
    440 
    441   if ((res = expectSPACE()) != ACCEPT) {
    442     session.setLastError("Expected SPACE after FETCH SPACE set");
    443     return res;
    444   }
    445 
    446   BincImapParserFetchAtt f;
    447 
    448   if ((res = expectThisString("ALL")) == ACCEPT) {
    449     f.type = "ALL";
    450     c_in.fatt.push_back(f);
    451   } else if ((res = expectThisString("FULL")) == ACCEPT) {
    452     f.type = "FULL";
    453     c_in.fatt.push_back(f);
    454   } else if ((res = expectThisString("FAST")) == ACCEPT) {
    455     f.type = "FAST";
    456     c_in.fatt.push_back(f);
    457   } else if ((res = expectFetchAtt(f)) == ACCEPT) {
    458     c_in.fatt.push_back(f);
    459   } else if ((res = expectThisString("(")) == ACCEPT) {
    460     while (1) {
    461       BincImapParserFetchAtt ftmp;
    462       if ((res = expectFetchAtt(ftmp)) != ACCEPT) {
    463 	session.setLastError("Expected fetch_att");
    464 	return res;
    465       }
    466 	
    467       c_in.fatt.push_back(ftmp);
    468       
    469       if ((res = expectSPACE()) == REJECT)
    470 	break;
    471       else if (res == ERROR)
    472 	return ERROR;
    473     }
    474 
    475     if ((res = expectThisString(")")) != ACCEPT) {
    476       session.setLastError("Expected )");
    477       return res;
    478     }
    479   } else {
    480     session.setLastError("Expected ALL, FULL, FAST, fetch_att or (");
    481     return res;
    482   }
    483 
    484   if ((res = expectCRLF()) != ACCEPT) {
    485     session.setLastError("Expected CRLF");
    486     return res;
    487   }
    488 
    489   c_in.setName("FETCH");
    490   return ACCEPT;
    491 }
    492 
    493 //----------------------------------------------------------------------
    494 Operator::ParseResult
    495 FetchOperator::expectSectionText(BincImapParserFetchAtt &f_in) const
    496 {
    497   Session &session = Session::getInstance();
    498 
    499   Operator::ParseResult res;
    500   if ((res = expectThisString("HEADER")) == ACCEPT) {
    501     f_in.sectiontext = "HEADER";
    502     
    503     if ((res = expectThisString(".FIELDS")) == ACCEPT) {
    504       f_in.sectiontext += ".FIELDS";
    505       
    506       if ((res = expectThisString(".NOT")) == ACCEPT)
    507 	f_in.sectiontext += ".NOT";
    508       
    509       if ((res = expectSPACE()) != ACCEPT) {
    510 	session.setLastError("expected SPACE");
    511 	return res;
    512       }
    513       
    514       if ((res = expectHeaderList(f_in)) != ACCEPT) {
    515 	session.setLastError("Expected header_list");
    516 	return res;
    517       }
    518     }
    519   } else if ((res = expectThisString("TEXT")) == ACCEPT)
    520     f_in.sectiontext = "TEXT";
    521   else
    522     return REJECT;
    523 
    524   return ACCEPT;
    525 
    526 }
    527 
    528 //----------------------------------------------------------------------
    529 Operator::ParseResult
    530 FetchOperator::expectSection(BincImapParserFetchAtt &f_in) const
    531 {
    532   Session &session = Session::getInstance();
    533 
    534   Operator::ParseResult res;
    535   if ((res = expectThisString("[")) != ACCEPT)
    536     return REJECT;
    537     
    538   if ((res = expectSectionText(f_in)) != ACCEPT) {
    539     unsigned int n;
    540     if ((res = expectNZNumber(n)) == ACCEPT) {
    541       
    542       BincStream nstr;
    543 	
    544       nstr << n;
    545 
    546       f_in.section = nstr.str();
    547       
    548       bool gotadotalready = false;
    549       while (1) {
    550 	if ((res = expectThisString(".")) != ACCEPT)
    551 	  break;
    552 	
    553 	if ((res = expectNZNumber(n)) != ACCEPT) {
    554 	  gotadotalready = true;
    555 	  break;
    556 	}
    557 	
    558 	f_in.section += ".";	
    559 	
    560 	BincStream nstr;
    561 	
    562 	nstr << n;
    563 	
    564 	f_in.section += nstr.str();	 
    565       }
    566       
    567       if (gotadotalready || (res = expectThisString(".")) == ACCEPT) {
    568 	if ((res = expectThisString("MIME")) == ACCEPT) {
    569 	  f_in.sectiontext = "MIME";
    570 	} else if ((res = expectSectionText(f_in)) != ACCEPT) {
    571 	  session.setLastError("Expected MIME or section_text");
    572 	  return res;
    573 	}
    574       }
    575     }
    576   }
    577 
    578   if ((res = expectThisString("]")) != ACCEPT) {
    579     session.setLastError("Expected ]");
    580     return res;
    581   }
    582 
    583   return ACCEPT;
    584 }
    585 
    586 //----------------------------------------------------------------------
    587 Operator::ParseResult
    588 FetchOperator::expectHeaderList(BincImapParserFetchAtt &f_in) const
    589 {
    590   Session &session = Session::getInstance();
    591 
    592   Operator::ParseResult res;
    593   if ((res = expectThisString("(")) != ACCEPT)
    594     return REJECT;
    595 
    596   string header_fld_name;
    597   while (1) {
    598     if ((res = expectAstring(header_fld_name)) != ACCEPT) {
    599       session.setLastError("Expected header_fld_name");
    600       return res;
    601     }
    602 
    603     f_in.headerlist.push_back(header_fld_name);
    604 
    605     if ((res = expectSPACE()) == ACCEPT)
    606       continue;
    607     else break;
    608   }
    609 
    610   if ((res = expectThisString(")")) != ACCEPT) {
    611     session.setLastError("Expected )");
    612     return res;
    613   }
    614 
    615   return ACCEPT;
    616 }
    617 
    618 //----------------------------------------------------------------------
    619 Operator::ParseResult
    620 FetchOperator::expectOffset(BincImapParserFetchAtt &f_in) const
    621 {
    622   Session &session = Session::getInstance();
    623   Operator::ParseResult res;
    624 
    625   if ((res = expectThisString("<")) != ACCEPT)
    626     return REJECT;
    627 
    628   unsigned int i;
    629   if ((res = expectNumber(i)) != ACCEPT) {
    630     session.setLastError("Expected number");
    631     return res;
    632   }
    633 
    634   if ((res = expectThisString(".")) != ACCEPT) {
    635     session.setLastError("Expected .");
    636     return res;
    637   }
    638 
    639   unsigned int j;
    640   if ((res = expectNZNumber(j)) != ACCEPT) {
    641     session.setLastError("expected nz_number");
    642     return res;
    643   }
    644 
    645   if ((res = expectThisString(">")) != ACCEPT) {
    646     session.setLastError("Expected >");
    647     return res;
    648   }
    649 
    650   f_in.offsetstart = i;
    651   f_in.offsetlength = j;
    652   return ACCEPT;
    653 }
    654 
    655 //----------------------------------------------------------------------
    656 Operator::ParseResult
    657 FetchOperator::expectFetchAtt(BincImapParserFetchAtt &f_in) const
    658 {
    659   Operator::ParseResult res;
    660 
    661   Session &session = Session::getInstance();
    662 
    663   if ((res = expectThisString("ENVELOPE")) == ACCEPT) f_in.type = "ENVELOPE";
    664   else if ((res = expectThisString("FLAGS")) == ACCEPT) f_in.type = "FLAGS";
    665   else if ((res = expectThisString("INTERNALDATE")) == ACCEPT)
    666     f_in.type = "INTERNALDATE";
    667   else if ((res = expectThisString("UID")) == ACCEPT) f_in.type = "UID";
    668   else if ((res = expectThisString("RFC822")) == ACCEPT) {
    669     f_in.type = "RFC822";
    670     if ((res = expectThisString(".HEADER")) == ACCEPT) f_in.type += ".HEADER";
    671     else if ((res = expectThisString(".SIZE")) == ACCEPT) f_in.type += ".SIZE";
    672     else if ((res = expectThisString(".TEXT")) == ACCEPT) f_in.type += ".TEXT";
    673     else if ((res = expectThisString(".")) == ACCEPT) {
    674       session.setLastError("Expected RFC822, RFC822.HEADER,"
    675 			   " RFC822.SIZE or RFC822.TEXT");
    676       return ERROR;
    677     }
    678 
    679   } else if ((res = expectThisString("BODY")) == ACCEPT) {
    680     f_in.type = "BODY";
    681 
    682     if ((res = expectThisString("STRUCTURE")) == ACCEPT) f_in.type += "STRUCTURE";
    683     else if ((res = expectThisString(".PEEK")) == ACCEPT) f_in.type += ".PEEK";
    684       
    685     if ((res = expectSection(f_in)) != ACCEPT)
    686       f_in.hassection = false;
    687     else {
    688       f_in.hassection = true;
    689 
    690       if ((res = expectOffset(f_in)) == ERROR)
    691 	return ERROR;
    692     } 
    693   } else
    694     return REJECT;
    695 
    696   return ACCEPT;
    697 }