1/* 2 Copyright (C) 2000-2007 SKYRIX Software AG 3 4 This file is part of SOPE. 5 6 SOPE is free software; you can redistribute it and/or modify it under 7 the terms of the GNU Lesser General Public License as published by the 8 Free Software Foundation; either version 2, or (at your option) any 9 later version. 10 11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14/bin/bash: 58: command not found 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with SOPE; see the file COPYING. If not, write to the 18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 19 02111-1307, USA. 20*/ 21 22#include "NGImap4ResponseParser.h" 23#include "NGImap4Support.h" 24#include "NGImap4Envelope.h" 25#include "NGImap4EnvelopeAddress.h" 26#include "imCommon.h" 27 28// TODO(hh): code is now prepared for last-exception, but currently it just 29// raises and may leak the exception object 30 31@interface NGImap4ResponseParser(ParsingPrivates) 32- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_; 33- (NSDictionary *)_parseBodyContent; 34- (NSData *) _parseBodyHeaderFields; 35 36- (NSData *)_parseData; 37 38- (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_; 39- (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_; 40- (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_; 41- (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_; 42- (BOOL)_parseNamespaceResponseIntoHashMap:(NGMutableHashMap *)result_; 43- (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_; 44- (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_; 45- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_; 46- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_; 47- (BOOL)_parseThreadResponseIntoHashMap:(NGMutableHashMap *)result_; 48- (BOOL)_parseVanishedResponseIntoHashMap:(NGMutableHashMap *)result_; 49- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_; 50- (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_; 51- (BOOL)_parseAnnotationResponseIntoHashMap:(NGMutableHashMap *)result_; 52- (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_; 53- (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_; 54 55- (NSArray *)_parseThread; 56 57@end 58 59@implementation NGImap4ResponseParser 60 61#define __la(__SELF__, __PEEKPOS) \ 62 ((__SELF__->la == NULL) \ 63 ? [__SELF__->buffer la:__PEEKPOS]\ 64 : __SELF__->la(__SELF__->buffer, @selector(la:), __PEEKPOS)) 65 66static __inline__ int _la(NGImap4ResponseParser *self, unsigned _laCnt) { 67 register unsigned char c = __la(self, _laCnt); 68 return (c == '\r') 69 ? _la(self, _laCnt + 1) 70 : c; 71} 72static __inline__ BOOL _matchesString(NGImap4ResponseParser *self, 73 const char *s) 74{ 75 register unsigned int i; 76 77 for (i = 0; s[i] != '\0'; i++) { 78 if (_la(self, i) != s[i]) 79 return NO; 80 } 81 return YES; 82} 83 84static NSDictionary *_parseBody(NGImap4ResponseParser *self, 85 BOOL isBodyStructure); 86static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self, 87 BOOL isBodyStructure); 88static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self, 89 BOOL isBodyStructure); 90 91static NSArray *_parseLanguages(); 92 93static NSString *_parseBodyString(NGImap4ResponseParser *self, 94 BOOL _convertString); 95static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self, 96 BOOL _convertString, 97 BOOL _decode); 98static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self); 99static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self); 100static NSArray *_parseAddressStructure(NGImap4ResponseParser *self); 101static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self); 102static int _parseTaggedResponse(NGImap4ResponseParser *self, 103 NGMutableHashMap *result_); 104static void _parseUntaggedResponse(NGImap4ResponseParser *self, 105 NGMutableHashMap *result_); 106static NSArray *_parseFlagArray(NGImap4ResponseParser *self); 107static BOOL _parseEnabledUntaggedResponse(NGImap4ResponseParser *self, 108 NGMutableHashMap *result_); 109static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self, 110 NGMutableHashMap *result_); 111static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self, 112 NGMutableHashMap *result_); 113static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self, 114 NGMutableHashMap *result_); 115static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self, 116 NGMutableHashMap *result_); 117static NSNumber *_parseUnsigned(NGImap4ResponseParser *self); 118static NSString *_parseUntil(NGImap4ResponseParser *self, char _c); 119static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2); 120 121static __inline__ NSException *_consumeIfMatch 122 (NGImap4ResponseParser *self, unsigned char _m); 123static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt); 124 125static void _parseSieveRespone(NGImap4ResponseParser *self, 126 NGMutableHashMap *result_); 127static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self, 128 NGMutableHashMap *result_); 129static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self, 130 NGMutableHashMap *result_); 131static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self, 132 NGMutableHashMap *result_); 133static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self, 134 NGMutableHashMap *result_); 135static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self); 136static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self); 137 138static unsigned int LaSize = 4097; 139static unsigned UseMemoryMappedData = 0; 140static unsigned Imap4MMDataBoundary = 0; 141static BOOL debugOn = NO; 142static BOOL debugDataOn = NO; 143static NSStringEncoding encoding; 144static Class StrClass = Nil; 145static Class NumClass = Nil; 146static Class DataClass = Nil; 147static NSStringEncoding defCStringEncoding; 148static NSNumber *YesNum = nil; 149static NSNumber *NoNum = nil; 150static NSNull *null = nil; 151 152+ (void)initialize { 153 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; 154 static BOOL didInit = NO; 155 if (didInit) return; 156 didInit = YES; 157 158 null = [[NSNull null] retain]; 159 160 encoding = [NGMimePartParser defaultHeaderFieldEncoding]; 161 defCStringEncoding = [NSString defaultCStringEncoding]; 162 163 debugOn = [ud boolForKey:@"ImapDebugEnabled"]; 164 debugDataOn = [ud boolForKey:@"ImapDebugDataEnabled"]; 165 UseMemoryMappedData = [ud boolForKey:@"NoMemoryMappedDataForImapBlobs"]?0:1; 166 Imap4MMDataBoundary = [ud integerForKey:@"Imap4MMDataBoundary"]; 167 168 if (Imap4MMDataBoundary < 10) 169 /* Note: this should be larger than a usual header size! */ 170 Imap4MMDataBoundary = 2 * LaSize; 171 172 StrClass = [NSString class]; 173 NumClass = [NSNumber class]; 174 DataClass = [NSData class]; 175 YesNum = [[NumClass numberWithBool:YES] retain]; 176 NoNum = [[NumClass numberWithBool:NO] retain]; 177} 178 179+ (id)parserWithStream:(id<NGActiveSocket>)_stream { 180 NGImap4ResponseParser *parser; 181 182 parser = [NGImap4ResponseParser alloc]; /* seperate line to keep gcc happy */ 183 return [[parser initWithStream:_stream] autorelease]; 184} 185 186- (id)initWithStream:(id<NGActiveSocket>)_stream { 187 // designated initializer 188 if (_stream == nil) { 189 [self logWithFormat:@"%s: got no stream ...", __PRETTY_FUNCTION__]; 190 [self release]; 191 return nil; 192 } 193 194 if ((self = [super init])) { 195 id s; 196 197 s = [(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_stream]; 198 self->buffer = [NGByteBuffer alloc]; 199 self->buffer = [self->buffer initWithSource:s la:LaSize]; 200 [s release]; 201 202 if ([self->buffer respondsToSelector:@selector(methodForSelector:)]) 203 self->la = (int(*)(id, SEL, unsigned)) 204 [self->buffer methodForSelector:@selector(la:)]; 205 206 self->debug = debugOn; 207 } 208 return self; 209} 210 211- (id)init { 212 [self release]; 213 [NSException raise:@"InvalidUseOfMethodException" 214 format: 215 @"calling -init on the NGImap4ResponseParser is not allowed"]; 216 return nil; 217} 218 219- (void)dealloc { 220 [self->buffer release]; 221 if (self->debug) 222 [self->serverResponseDebug release]; 223 [super dealloc]; 224} 225 226/* exception handling */ 227 228- (void)setLastException:(NSException *)_exc { 229 // TODO: support last exception 230 [_exc raise]; 231} 232 233/* 234** Parse Sieve Responses 235*/ 236 237- (NGHashMap *)parseSieveResponse { 238 NGMutableHashMap *result; 239 240 if (self->debug) { 241 if (self->serverResponseDebug != nil) 242 [self->serverResponseDebug release]; 243 self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512]; 244 } 245 result = [NGMutableHashMap hashMapWithCapacity:64]; 246 247 if (_la(self,0) == -1) { 248 [self setLastException:[self->buffer lastException]]; 249 return nil; 250 } 251 252 _parseSieveRespone(self, result); 253 return result; 254} 255 256- (NGHashMap *)parseResponseForTagId:(int)_tag exception:(NSException **)ex_ { 257 /* parse a response from the server, _tag!=-1 parse until tagged response */ 258 // TODO: is NGHashMap really necessary here? 259 BOOL endOfCommand; 260 NGMutableHashMap *result; 261 262 if (ex_) *ex_ = nil; 263 264 if (self->debug) { 265 [self->serverResponseDebug release]; self->serverResponseDebug = nil; 266 self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512]; 267 } 268 269 result = [NGMutableHashMap hashMapWithCapacity:64]; 270 for (endOfCommand = NO; !endOfCommand; ) { 271 unsigned char l0; 272 273 l0 = _la(self, 0); 274 275 if (l0 == '*') { /* those starting with '* ' */ 276 _parseUntaggedResponse(self, result); 277 if ([result objectForKey:@"bye"]) { 278 endOfCommand = YES; 279 } 280 else { 281 if (_tag == -1) { 282 if ([result objectForKey:@"ok"] != nil) 283 endOfCommand = YES; 284 } 285 } 286 } 287 else if (l0 == '+') { /* starting with a '+'? */ 288 [self _parseContinuationResponseIntoHashMap:result]; 289 endOfCommand = YES; 290 } 291 else if (isdigit(l0)) { 292 /* those starting with a number '24 ', eg '24 OK Completed' */ 293 endOfCommand = (_parseTaggedResponse(self, result) == _tag); 294 } 295 else if (l0 == (unsigned char) -1) { 296 if (ex_) { 297 *ex_ = [self->buffer lastException]; 298 if (!*ex_) 299 *ex_ 300 = [NSException exceptionWithName:@"UnexpectedEndOfStream" 301 reason:(@"the parsed stream ended" 302 @" unexpectedly") 303 userInfo:nil]; 304 } else { 305 [self setLastException: [self->buffer lastException]]; 306 } 307 endOfCommand = YES; 308 result = nil; 309 } 310 } 311 return result; 312} 313- (NGHashMap *)parseResponseForTagId:(int)_tag { 314 // DEPRECATED 315 NSException *e = nil; 316 NGHashMap *hm; 317 318 hm = [self parseResponseForTagId:_tag exception:&e]; 319 if (e) { 320 [self setLastException:e]; 321 return nil; 322 } 323 return hm; 324} 325 326static void _parseSieveRespone(NGImap4ResponseParser *self, 327 NGMutableHashMap *result_) 328{ 329 if (_parseGreetingsSieveResponse(self, result_)) 330 return; 331 if (_parseDataSieveResponse(self, result_)) // la: 1 332 return; 333 if (_parseOkSieveResponse(self, result_)) // la: 2 334 return; 335 if (_parseNoSieveResponse(self, result_)) // la: 2 336 return; 337} 338 339- (NSData *)_parseDataToFile:(unsigned)_size { 340 // TODO: move to own method 341 // TODO: do not use NGFileStream but just fopen/fwrite 342 static NSProcessInfo *Pi = nil; 343 NGFileStream *stream; 344 NSData *result; 345 unsigned char buf[LaSize + 2]; 346 unsigned char tmpBuf[LaSize + 2]; 347 unsigned wasRead = 0; 348 NSString *path; 349 signed char lastChar; // must be signed 350 351 if (debugDataOn) [self logWithFormat:@" using memory mapped data ..."]; 352 353 if (Pi == nil) 354 Pi = [[NSProcessInfo processInfo] retain]; 355 356 path = [Pi temporaryFileName]; 357 stream = [NGFileStream alloc]; /* extra line to keep gcc happy */ 358 stream = [stream initWithPath:path]; 359 360 if (![stream openInMode:NGFileWriteOnly]) { 361 NSException *e; 362 363 e = [[NGImap4ParserException alloc] 364 initWithFormat:@"Could not open temporary file %@", path]; 365 [self setLastException:[e autorelease]]; 366 [self logWithFormat: [e reason]]; 367 return nil; 368 } 369 370 lastChar = -1; 371 while (wasRead < _size) { 372 unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt; 373 374 bufCnt = 0; 375 376 if (lastChar != -1) { 377 buf[bufCnt++] = lastChar; 378 lastChar = -1; 379 } 380 381 [self->buffer la:(_size - wasRead < LaSize) 382 ? (_size - wasRead) 383 : LaSize]; 384 385 readCnt = [self->buffer readBytes:buf+bufCnt count:_size - wasRead]; 386 387 wasRead+=readCnt; 388 bufCnt +=readCnt; 389 390 tmpSize = bufCnt - 1; 391 cnt = 0; 392 tmpBufCnt = 0; 393 394 while (cnt < tmpSize) { 395 if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) { 396 cnt++; 397 } 398 tmpBuf[tmpBufCnt++] = buf[cnt++]; 399 } 400 if (cnt < bufCnt) { 401 lastChar = buf[cnt]; 402 } 403 [stream writeBytes:tmpBuf count:tmpBufCnt]; 404 } 405 if (lastChar != -1) 406 [stream writeBytes:&lastChar count:1]; 407 408 [stream close]; 409 [stream release]; stream = nil; 410 result = [DataClass dataWithContentsOfMappedFile:path]; 411 [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; 412 413 return result; 414} 415- (NSData *)_parseDataIntoRAM:(unsigned)_size { 416 /* parses data into a RAM buffer (NSData) */ 417 unsigned char *buf = NULL; 418 unsigned char *tmpBuf; 419 unsigned wasRead = 0; 420 unsigned cnt, tmpBufCnt, tmpSize; 421 NSData *result; 422 423 buf = calloc(_size + 10, sizeof(char)); 424 425 while (wasRead < _size) { 426 [self->buffer la:(_size - wasRead < LaSize) ? (_size - wasRead) : LaSize]; 427 428 wasRead += [self->buffer readBytes:(buf + wasRead) count:(_size-wasRead)]; 429 } 430 431 /* normalize response \r\n -> \n */ 432 433 tmpBuf = calloc(_size + 10, sizeof(char)); 434 cnt = 0; 435 tmpBufCnt = 0; 436 tmpSize = _size == 0 ? 0 : _size - 1; 437 while (tmpBufCnt < tmpSize && cnt < _size) { 438 if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n')) 439 cnt++; /* skip \r */ 440 441 tmpBuf[tmpBufCnt] = buf[cnt]; 442 tmpBufCnt++; 443 cnt++; 444 } 445 if (cnt < _size) { 446 tmpBuf[tmpBufCnt] = buf[cnt]; 447 tmpBufCnt++; 448 cnt++; 449 } 450 451 result = [DataClass dataWithBytesNoCopy:tmpBuf length:tmpBufCnt]; 452 453 if (buf != NULL) free(buf); buf = NULL; 454 return result; 455} 456- (NSData *)_parseData { 457 /* 458 parses: 459 { <uint> } \n 460 */ 461 // TODO: split up method 462 unsigned size; 463 NSNumber *sizeNum; 464 465 if (_la(self, 0) != '{') 466 return nil; 467 468 if (debugDataOn) [self logWithFormat:@"parse data ..."]; 469 470 /* got header */ 471 472 _consume(self, 1); // '{' 473 if ((sizeNum = _parseUnsigned(self)) == nil) { 474 NSException *e; 475 476 e = [[NGImap4ParserException alloc] 477 initWithFormat:@"expect a number between {}"]; 478 [self setLastException:[e autorelease]]; 479 return nil; 480 } 481 if (debugDataOn) [self logWithFormat:@" parse data, size: %@", sizeNum]; 482 _consumeIfMatch(self, '}'); 483 _consumeIfMatch(self, '\n'); 484 485 if ((size = [sizeNum intValue]) == 0) { 486 [self logWithFormat:@"ERROR(%s): got content size '0'!", 487 __PRETTY_FUNCTION__]; 488 return nil; 489 } 490 491 if (UseMemoryMappedData && (size > Imap4MMDataBoundary)) 492 return [self _parseDataToFile:size]; 493 494 return [self _parseDataIntoRAM:size]; 495} 496 497/* 498 Similair to _parseData but used to parse something like this : 499 500 BODY[HEADER.FIELDS (X-PRIORITY)] {17} 501 X-Priority: 1 502 503 ) 504 505 Headers are returned as data, as is. 506*/ 507- (NSData *) _parseBodyHeaderFields 508{ 509 NSData *result; 510 unsigned size; 511 NSNumber *sizeNum; 512 513 result = nil; 514 515 /* We skip until we're ready to parse {length}. We must be careful 516 here and not assume we will have a valid length - we could very 517 well receive NIL from the server */ 518 _parseUntil(self, ' '); 519 520 if ((_la(self,0)) == '{') 521 { 522 _consume(self, 1); 523 524 if ((sizeNum = _parseUnsigned(self)) == nil) { 525 NSException *e; 526 527 e = [[NGImap4ParserException alloc] 528 initWithFormat:@"expect a number between {}"]; 529 [self setLastException:[e autorelease]]; 530 return nil; 531 } 532 _consumeIfMatch(self, '}'); 533 _consumeIfMatch(self, '\n'); 534 535 if ((size = [sizeNum intValue]) == 0) { 536 [self logWithFormat:@"ERROR(%s): got content size '0'!", 537 __PRETTY_FUNCTION__]; 538 return nil; 539 } 540 541 if (UseMemoryMappedData && (size > Imap4MMDataBoundary)) 542 return [self _parseDataToFile:size]; 543 544 return [self _parseDataIntoRAM:size]; 545 } 546 547 return result; 548} 549 550static int _parseTaggedResponse(NGImap4ResponseParser *self, 551 NGMutableHashMap *result_) 552{ 553 NSDictionary *d; 554 NSNumber *tag = nil; 555 NSString *res = nil; 556 NSString *desc = nil; 557 NSString *flag = nil; 558 559 if ((tag = _parseUnsigned(self)) == nil) { 560 NSException *e; 561 562 if (self->debug) { 563 e = [[NGImap4ParserException alloc] 564 initWithFormat:@"expect a number at begin of tagged response <%@>", 565 self->serverResponseDebug]; 566 } 567 else { 568 e = [[NGImap4ParserException alloc] 569 initWithFormat:@"expect a number at begin of tagged response"]; 570 } 571 e = [e autorelease]; 572 [self setLastException:e]; 573 return -1; 574 } 575 576 _consumeIfMatch(self, ' '); 577 res = [_parseUntil(self, ' ') lowercaseString]; 578 if (_la(self, 0) == '[') { /* Found flag like [READ-ONLY] */ 579 _consume(self, 1); 580 flag = _parseUntil(self, ']'); 581 } 582 desc = _parseUntil(self, '\n'); 583 /* 584 ATTENTION: if no flag was set, flag == nil, in this case all key-value 585 pairs after flag will be ignored 586 */ 587 d = [[NSDictionary alloc] initWithObjectsAndKeys: 588 tag, @"tagId", 589 res, @"result", 590 desc, @"description", 591 flag, @"flag", nil]; 592 [result_ addObject:d forKey:@"ResponseResult"]; 593 [d release]; 594 return [tag intValue]; 595} 596 597static void _parseUntaggedResponse(NGImap4ResponseParser *self, 598 NGMutableHashMap *result_) 599{ 600 // TODO: is it really required by IMAP4 that responses are uppercase? 601 // TODO: apparently this code *breaks* with lowercase detection on! 602 unsigned char l0, l1 = 0; 603 _consumeIfMatch(self, '*'); 604 _consumeIfMatch(self, ' '); 605 606 l0 = _la(self, 0); 607 switch (l0) { 608 case 'A': 609 l1 = _la(self, 1); 610 if (l1 == 'C' && [self _parseACLResponseIntoHashMap:result_]) 611 return; 612 if (l1 == 'N' && [self _parseAnnotationResponseIntoHashMap:result_]) 613 return; 614 break; 615 616 case 'B': 617 l1 = _la(self, 1); 618 if (l1 == 'A' && _parseBadUntaggedResponse(self, result_)) // la: 3 619 return; 620 if (l1 == 'Y' && [self _parseByeUntaggedResponseIntoHashMap:result_]) // 3 621 return; 622 break; 623 624 case 'C': 625 if ([self _parseCapabilityResponseIntoHashMap:result_]) // la: 10 626 return; 627 break; 628 629 case 'E': 630 if (_parseEnabledUntaggedResponse(self, result_)) // la: 7 631 return; 632 break; 633 634 case 'F': 635 if (_parseFlagsUntaggedResponse(self, result_)) // la: 5 636 return; 637 break; 638 639 // ID untagged response 640 case 'I': 641 _parseUntil(self, '\n'); 642 return; 643 644 case 'L': 645 if (_matchesString(self, "LISTRIGHTS")) { 646 if ([self _parseListRightsResponseIntoHashMap:result_]) 647 return; 648 } 649 if ([self _parseListOrLSubResponseIntoHashMap:result_]) // la: 4 650 return; 651 break; 652 653 case 'M': 654 if ([self _parseMyRightsResponseIntoHashMap:result_]) 655 return; 656 break; 657 658 case 'N': 659 if (_matchesString(self, "NAMESPACE")) { 660 if ([self _parseNamespaceResponseIntoHashMap:result_]) 661 return; 662 } 663 if (_parseNoUntaggedResponse(self, result_)) // la: 2 664 return; 665 break; 666 667 case 'O': 668 if (_parseOkUntaggedResponse(self, result_)) // la: 2 669 /* eg "* OK Completed" */ 670 return; 671 break; 672 673 case 'R': 674 break; 675 676 case 'S': 677 switch (_la(self, 1)) { 678 case 'O': // SORT 679 if ([self _parseSortResponseIntoHashMap:result_]) // la: 4 680 return; 681 break; 682 case 'E': // SEARCH 683 if ([self _parseSearchResponseIntoHashMap:result_]) // la: 5 684 return; 685 break; 686 case 'T': // STATUS 687 if ([self _parseStatusResponseIntoHashMap:result_]) // la: 6 688 /* eg "* STATUS INBOX (MESSAGES 0 RECENT 0 UNSEEN 0)" */ 689 return; 690 break; 691 } 692 break; 693 694 case 'T': 695 if ([self _parseThreadResponseIntoHashMap:result_]) // la: 6 696 return; 697 break; 698 699 case 'V': 700 if ([self _parseVanishedResponseIntoHashMap:result_]) // la: 6 701 return; 702 break; 703 704 case 'Q': 705 if ([self _parseQuotaResponseIntoHashMap:result_]) // la: 6 706 return; 707 if ([self _parseQuotaRootResponseIntoHashMap:result_]) // la: 10 708 return; 709 break; 710 711 case '0': case '1': case '2': case '3': case '4': 712 case '5': case '6': case '7': case '8': case '9': 713 if ([self _parseNumberUntaggedResponse:result_]) // la: 5 714 /* eg "* 928 FETCH ..." */ 715 return; 716 break; 717 } 718 719 // TODO: what if none matches? 720 [self logWithFormat:@"%s: no matching tag specifier?", __PRETTY_FUNCTION__]; 721 [self logWithFormat:@" line: '%@'", _parseUntil(self, '\n')]; 722} 723 724- (void)_parseContinuationResponseIntoHashMap:(NGMutableHashMap *)result_ { 725 _consumeIfMatch(self, '+'); 726 _consumeIfMatch(self, ' '); 727 728 [result_ addObject:YesNum forKey:@"ContinuationResponse"]; 729 [result_ addObject:_parseUntil(self, '\n') forKey:@"description"]; 730} 731 732static inline void 733_purifyQuotedString(NSMutableString *quotedString) { 734 unichar *currentChar, *qString, *maxC, *startC; 735 unsigned int max, questionMarks; 736 BOOL possiblyQuoted, skipSpaces; 737 NSMutableString *newString; 738 739 newString = [NSMutableString string]; 740 741 max = [quotedString length]; 742 qString = malloc (sizeof (unichar) * max); 743 [quotedString getCharacters: qString]; 744 currentChar = qString; 745 startC = qString; 746 maxC = qString + max; 747 748 possiblyQuoted = NO; 749 skipSpaces = NO; 750 751 questionMarks = 0; 752 753 while (currentChar < maxC) { 754 if (possiblyQuoted) { 755 if (questionMarks == 2) { 756 if ((*currentChar == 'Q' || *currentChar == 'q' 757 || *currentChar == 'B' || *currentChar == 'b') 758 && ((currentChar + 1) < maxC 759 && (*(currentChar + 1) == '?'))) { 760 currentChar++; 761 questionMarks = 3; 762 } 763 else { 764 possiblyQuoted = NO; 765 } 766 } 767 else if (questionMarks == 4) { 768 if (*currentChar == '=') { 769 skipSpaces = YES; 770 possiblyQuoted = NO; 771 currentChar++; 772 [newString appendString: [NSString stringWithCharacters: startC 773 length: (currentChar - startC)]]; 774 startC = currentChar; 775 } 776 else { 777 possiblyQuoted = NO; 778 } 779 } 780 else { 781 if (*currentChar == '?') { 782 questionMarks++; 783 } 784 else if (*currentChar == ' ' && questionMarks != 3) { 785 possiblyQuoted = NO; 786 } 787 } 788 } 789 else if (*currentChar == '=' 790 && ((currentChar + 1) < maxC 791 && (*(currentChar + 1) == '?'))) { 792 [newString appendString: [NSString stringWithCharacters: startC 793 length: (currentChar - startC)]]; 794 startC = currentChar; 795 possiblyQuoted = YES; 796 skipSpaces = NO; 797 currentChar++; 798 questionMarks = 1; 799 } 800 801 if (skipSpaces) { 802 /* This part is about skipping the spaces separating two encoded chunks, 803 which occurs when the chunks are on different lines. However we 804 cannot ignore them if the next chunk is not encoded. Basically, we 805 can deduce a case from the other by the fact that it makes no sense 806 per se to have a space separating two encoded chunks. */ 807 startC = currentChar; 808 while (currentChar < maxC 809 && (*currentChar == ' ' || *currentChar == '\t')) 810 currentChar++; 811 if (currentChar != startC) { 812 if (currentChar < maxC && *currentChar != '=') 813 [newString appendString: [NSString stringWithCharacters: startC 814 length: (currentChar - startC)]]; 815 startC = currentChar; 816 } 817 else 818 currentChar++; 819 820 skipSpaces = NO; 821 } 822 else 823 currentChar++; 824 } 825 826 if (startC < maxC) 827 [newString appendString: [NSString stringWithCharacters: startC 828 length: (currentChar - startC)]]; 829 830 [quotedString setString: newString]; 831 free (qString); 832} 833 834- (NSString *)_parseQuotedString { 835 NSMutableString *quotedString; 836 NSString *tmpString; 837 838 /* parse a quoted string, eg '"' */ 839 if (_la(self, 0) == '"') { 840 _consume(self, 1); 841 quotedString = [NSMutableString string]; 842 tmpString = _parseUntil(self, '"'); 843 [quotedString appendString: tmpString]; 844 } 845 else { 846 quotedString = nil; 847 } 848 849 _purifyQuotedString(quotedString); 850 851 return quotedString; 852} 853 854- (NSString *)_parseQuotedStringOrNIL { 855 unsigned char c0; 856 857 if ((c0 = _la(self, 0)) == '"') 858 return [self _parseQuotedString]; 859 860 if (c0 == '{') { 861 /* a size indicator, eg '{112}\nkasdjfkja sdj fhj hasdfj hjasdf' */ 862 NSData *data; 863 NSString *s; 864 865 if ((data = [self _parseData]) == nil) 866 return nil; 867 if (![data isNotEmpty]) 868 return @""; 869 870 s = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 871 if (s == nil) 872 s = [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding]; 873 if (s == nil) { 874 [self logWithFormat: 875 @"ERROR(%s): could not convert data (%d bytes) into string.", 876 __PRETTY_FUNCTION__, [data length]]; 877 return @"[ERROR: NGImap4 could not parse IMAP4 data string]"; 878 } 879 return [s autorelease]; 880 } 881 882 if (c0 == 'N' && _matchesString(self, "NIL")) { 883 _consume(self, 3); 884 return (id)null; 885 } 886 return nil; 887} 888- (id)_parseQuotedStringOrDataOrNIL { 889 if (_la(self, 0) == '"') 890 return [self _parseQuotedString]; 891 if (_la(self, 0) == '{') 892 return [self _parseData]; 893 894 if (_matchesString(self, "NIL")) { 895 _consume(self, 3); 896 return null; 897 } 898 return nil; 899} 900- (void)_consumeOptionalSpace { 901 if (_la(self, 0) == ' ') _consume(self, 1); 902} 903 904- (BOOL)_parseListOrLSubResponseIntoHashMap:(NGMutableHashMap *)result_ { 905 NSArray *flags = nil; 906 NSString *delim = nil; 907 NSString *name = nil; 908 NSDictionary *d; 909 910 if (!_matchesString(self, "LIST ") && !_matchesString(self, "LSUB ")) 911 return NO; 912 913 _consume(self, 5); /* consume 'LIST ' or 'LSUB ' */ 914 flags = _parseFlagArray(self); 915 _consumeIfMatch(self, ' '); 916 917 if (_la(self, 0) == '"') { 918 delim = [self _parseQuotedString]; 919 _consumeIfMatch(self, ' '); 920 } 921 else { 922 _parseUntil(self, ' '); 923 delim = nil; 924 } 925 if (_la(self, 0) == '"') { 926 name = [self _parseQuotedString]; 927 _parseUntil(self, '\n'); 928 } 929 else if (_la(self, 0) == '{') { 930 name = [self _parseQuotedStringOrNIL]; 931 _parseUntil(self, '\n'); 932 } 933 else 934 name = _parseUntil(self, '\n'); 935 936 d = [[NSDictionary alloc] initWithObjectsAndKeys: 937 name, @"folderName", 938 flags, @"flags", 939 delim, @"delimiter", nil]; 940 [result_ addObject:d forKey:@"list"]; 941 [d release]; 942 return YES; 943} 944 945- (BOOL)_parseCapabilityResponseIntoHashMap:(NGMutableHashMap *)result_ { 946 NSString *caps; 947 NSEnumerator *enumerator; 948 id obj; 949 NSMutableArray *array; 950 NSArray *tmp; 951 952 if (!_matchesString(self, "CAPABILITY ")) 953 return NO; 954 955 caps = _parseUntil(self, '\n'); 956 957 array = [[NSMutableArray alloc] initWithCapacity:16]; 958 959 enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator]; 960 while ((obj = [enumerator nextObject]) != nil) 961 [array addObject:[obj lowercaseString]]; 962 963 tmp = [array copy]; 964 [result_ addObject:tmp forKey:@"capability"]; 965 966 [array release]; array = nil; 967 [tmp release]; tmp = nil; 968 return YES; 969} 970 971/* support for NAMESPACE extension - RFC2342 */ 972 973- (NSDictionary *)_parseNamespacePart { 974 NSDictionary *namespacePart; 975 NSString *prefix, *key, *delimiter; 976 NSMutableDictionary *parameters; 977 NSMutableArray *values; 978 979 _consume(self, 1); /* ( */ 980 prefix = [self _parseQuotedStringOrNIL]; /* "prefix" */ 981 _consume(self, 1); /* <sp> */ 982 delimiter = [self _parseQuotedStringOrNIL]; /* "delimiter" */ 983 parameters = [NSMutableDictionary dictionary]; 984 while (_la(self, 0) == ' ') { 985 _consume(self, 1); /* <sp> */ 986 key = [self _parseQuotedString]; 987 _consume(self, 1); /* <sp> */ 988 values = [NSMutableArray new]; 989 while (_la(self, 0) != ')') { 990 _consume(self, 1); /* ( or <sp> */ 991 [values addObject: [self _parseQuotedString]]; 992 } 993 _consume(self, 1); /* ) */ 994 [parameters setObject: values forKey: key]; 995 [values release]; 996 } 997 _consume(self, 1); /* ) */ 998 999 namespacePart = [NSDictionary dictionaryWithObjectsAndKeys: 1000 prefix, @"prefix", 1001 delimiter, @"delimiter", 1002 parameters, @"parameters", 1003 nil]; 1004 1005 return namespacePart; 1006} 1007 1008- (NSArray *)_parseNamespace { 1009 NSMutableArray *namespace; 1010 1011 namespace = [[NSMutableArray alloc] initWithCapacity: 3]; 1012 if (_la(self, 0) == 'N') { 1013 namespace = nil; 1014 _consume(self, 3); 1015 } else { 1016 _consume(self, 1); /* ( */ 1017 while (_la(self, 0) == '(') { 1018 [namespace addObject: [self _parseNamespacePart]]; 1019 } 1020 _consume(self, 1); /* ) */ 1021 } 1022 1023 return namespace; 1024} 1025 1026- (BOOL)_parseNamespaceResponseIntoHashMap:(NGMutableHashMap *)result_ { 1027 NSArray *namespace; 1028 1029 if (!_matchesString(self, "NAMESPACE ")) 1030 return NO; 1031 1032 _parseUntil(self, ' '); 1033 1034 namespace = [self _parseNamespace]; 1035 if (namespace) 1036 [result_ addObject:namespace forKey:@"personal"]; 1037 _consume(self, 1); 1038 namespace = [self _parseNamespace]; 1039 if (namespace) 1040 [result_ addObject:namespace forKey:@"other users"]; 1041 _consume(self, 1); 1042 namespace = [self _parseNamespace]; 1043 if (namespace) 1044 [result_ addObject:namespace forKey:@"shared"]; 1045 _consume(self, 1); /* \n */ 1046 1047 return YES; 1048} 1049 1050- (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_ { 1051 /* 1052 21 GETACL INBOX 1053 * ACL INBOX test.et.di.cete-lyon lrswipcda helge lrwip "a group" lrs fred "" 1054 */ 1055 NSString *uid; 1056 NSString *userRights; 1057 NSString *mailbox; 1058 NSMutableArray *uids; 1059 NSMutableArray *rights; 1060 NSDictionary *result; 1061 1062 if (!_matchesString(self, "ACL ")) 1063 return NO; 1064 _consume(self, 4); 1065 1066 if ((mailbox = _parseBodyString(self, YES)) != nil) 1067 [result_ setObject:mailbox forKey:@"mailbox"]; 1068 _consumeIfMatch(self, ' '); 1069 1070 uids = [[NSMutableArray alloc] initWithCapacity:8]; 1071 rights = [[NSMutableArray alloc] initWithCapacity:8]; 1072 1073 while (_la(self, 0) != '\n') { 1074 if (_la(self, 0) == '"') { 1075 uid = [self _parseQuotedString]; 1076 _consumeIfMatch(self, ' '); 1077 } 1078 else 1079 uid = _parseUntil(self, ' ' ); 1080 1081 if (_la(self, 0) == '"') 1082 userRights = [self _parseQuotedString]; 1083 else 1084 userRights = _parseUntil2(self, ' ', '\n'); 1085 [self _consumeOptionalSpace]; 1086 1087 [uids addObject:uid]; 1088 [rights addObject:userRights]; 1089 } 1090 _consume(self,1); 1091 1092 result = [[NSDictionary alloc] initWithObjects:rights forKeys:uids]; 1093 [result_ addObject:result forKey:@"acl"]; 1094 1095 [uids release]; uids = nil; 1096 [rights release]; rights = nil; 1097 [result release]; result = nil; 1098 return YES; 1099} 1100 1101- (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_ { 1102 /* 1103 Raw Sample (Cyrus): 1104 18 myrights INBOX 1105 * MYRIGHTS INBOX lrswipcda 1106 18 OK Completed 1107 */ 1108 NSString *rights; 1109 id obj; 1110 1111 if (!_matchesString(self, "MYRIGHTS ")) 1112 return NO; 1113 _consume(self, 9); 1114 1115 if ((obj = _parseBodyString(self, YES)) != nil) 1116 [result_ setObject:obj forKey:@"mailbox"]; 1117 _consumeIfMatch(self, ' '); 1118 1119 rights = _parseUntil(self, '\n'); 1120 [result_ setObject:rights forKey:@"myrights"]; 1121 return YES; 1122} 1123 1124- (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_ { 1125 /* 1126 Raw Sample (Cyrus): 1127 22 LISTRIGHTS INBOX helge 1128 * LISTRIGHTS INBOX helge "" l r s w i p c d a 0 1 2 3 4 5 6 7 8 9 1129 22 OK Completed 1130 */ 1131 NSString *rights; 1132 id obj; 1133 1134 if (!_matchesString(self, "LISTRIGHTS ")) 1135 return NO; 1136 _consume(self, 11); 1137 1138 if ((obj = _parseBodyString(self, YES)) != nil) 1139 [result_ setObject:obj forKey:@"mailbox"]; 1140 _consumeIfMatch(self, ' '); 1141 1142 if ((obj = _parseBodyString(self, YES)) != nil) 1143 [result_ setObject:obj forKey:@"uid"]; 1144 _consumeIfMatch(self, ' '); 1145 1146 if ((obj = _parseUntil(self, ' ')) != nil) { 1147 if ([obj isEqual:@"\"\""]) 1148 obj = @""; 1149 [result_ setObject:obj forKey:@"requiredRights"]; 1150 } 1151 1152 rights = _parseUntil(self, '\n'); 1153 [result_ setObject:[rights componentsSeparatedByString:@" "] 1154 forKey:@"listrights"]; 1155 return YES; 1156} 1157 1158- (BOOL)_parseSearchResponseIntoHashMap:(NGMutableHashMap *)result_ { 1159 NSMutableArray *msn = nil; 1160 NSNumber *n; 1161 1162 if (!_matchesString(self, "SEARCH")) 1163 return NO; 1164 1165 _consume(self, 6); 1166 1167 msn = [NSMutableArray arrayWithCapacity:128]; 1168 1169 while (_la(self, 0) == ' ') { 1170 _consume(self, 1); 1171 n = _parseUnsigned(self); 1172 if (n) 1173 [msn addObject:n]; 1174 } 1175 _parseUntil(self, '\n'); 1176 [result_ addObject:msn forKey:@"search"]; 1177 return YES; 1178} 1179 1180- (BOOL)_parseSortResponseIntoHashMap:(NGMutableHashMap *)result_ { 1181 NSMutableArray *msn = nil; 1182 1183 if (!_matchesString(self, "SORT")) 1184 return NO; 1185 1186 _consume(self, 4); 1187 1188 msn = [NSMutableArray arrayWithCapacity:128]; 1189 1190 while (_la(self, 0) == ' ') { 1191 _consume(self, 1); 1192 if (_la(self, 0) == '(') { 1193 _consume(self, 1); 1194 if (!_matchesString(self, "MODSEQ ")) 1195 return NO; 1196 _consume(self, 7); 1197 [result_ addObject:_parseUnsigned(self) forKey:@"modseq"]; 1198 _consume(self, 1); /* final ')' */ 1199 } 1200 else 1201 [msn addObject:_parseUnsigned(self)]; 1202 } 1203 _parseUntil(self, '\n'); 1204 [result_ addObject:msn forKey:@"sort"]; 1205 return YES; 1206} 1207 1208- (BOOL)_parseQuotaResponseIntoHashMap:(NGMutableHashMap *)result_ { 1209 NSString *qRoot; 1210 NSMutableDictionary *parse; 1211 NSMutableDictionary *quota; 1212 1213 if (!_matchesString(self, "QUOTA ")) 1214 return NO; 1215 1216 _consume(self, 6); 1217 1218 quota = [result_ objectForKey:@"quota"]; 1219 1220 if (quota == nil) { 1221 quota = [NSMutableDictionary dictionaryWithCapacity:2]; 1222 [result_ setObject:quota forKey:@"quota"]; 1223 } 1224 1225 parse = [NSMutableDictionary dictionaryWithCapacity:3]; 1226 if (_la(self, 0) == '"') { 1227 NSMutableString *str; 1228 _consume(self, 1); 1229 str = [NSMutableString stringWithFormat: @"\"%@\"", _parseUntil(self, '"')]; 1230 qRoot = str; 1231 } 1232 else { 1233 qRoot = _parseUntil2(self, ' ', '\n'); 1234 } 1235 1236 if (_la(self, 0) == ' ') { 1237 _consume(self, 1); 1238 1239 if (_la(self, 0) == '(') { 1240 _consume(self,1); 1241 if (_la(self, 0) == ')') { /* empty quota response */ 1242 _consume(self,1); 1243 } 1244 else { 1245 NSString *key; 1246 1247 key = _parseUntil(self, ' '); 1248 key = [key lowercaseString]; 1249 if ([key isEqualToString:@"storage"]) { 1250 NSString *used, *max; 1251 1252 used = _parseUntil(self, ' '); 1253 max = _parseUntil(self, ')'); 1254 1255 [parse setObject:used forKey:@"usedSpace"]; 1256 [parse setObject:max forKey:@"maxQuota"]; 1257 } 1258 else { 1259 NSString *v; 1260 1261 v = _parseUntil(self, ')'); 1262 1263 [parse setObject:v forKey:@"resource"]; 1264 } 1265 } 1266 } 1267 [quota setObject:parse forKey:qRoot]; 1268 } 1269 _parseUntil(self, '\n'); 1270 1271 return YES; 1272} 1273 1274- (BOOL)_parseQuotaRootResponseIntoHashMap:(NGMutableHashMap *)result_ { 1275 NSString *folderName, *folderRoot; 1276 NSMutableDictionary *dict; 1277 1278 if (!_matchesString(self, "QUOTAROOT ")) 1279 return NO; 1280 1281 _consume(self, 10); 1282 1283 dict = [result_ objectForKey:@"quotaRoot"]; 1284 1285 if (!dict) { 1286 dict = [NSMutableDictionary dictionaryWithCapacity:2]; 1287 [result_ setObject:dict forKey:@"quotaRoot"]; 1288 } 1289 if (_la(self, 0) == '"') { 1290 _consume(self , 1); 1291 folderName = _parseUntil(self, '"'); 1292 } 1293 else { 1294 folderName = _parseUntil2(self, '\n', ' '); 1295 } 1296 if (_la(self, 0) == ' ') { 1297 _consume(self, 1); 1298 folderRoot = _parseUntil(self, '\n'); 1299 } 1300 else { 1301 _consume(self, 1); 1302 folderRoot = nil; 1303 } 1304 if ([folderName isNotEmpty] && [folderRoot isNotEmpty]) 1305 [dict setObject:folderRoot forKey:folderName]; 1306 1307 return YES; 1308} 1309 1310- (NSArray *)_parseThread { 1311 NSMutableArray *array; 1312 NSNumber *msg; 1313 1314 array = [NSMutableArray arrayWithCapacity:64]; 1315 1316 if (_la(self, 0) == '(') 1317 _consume(self, 1); 1318 1319 while (1) { 1320 if (_la(self, 0) == '(') { 1321 NSArray *a; 1322 1323 a = [self _parseThread]; 1324 if (a != nil) [array addObject:a]; 1325 } 1326 else if ((msg = _parseUnsigned(self))) { 1327 [array addObject:msg]; 1328 } 1329 else { 1330 return nil; 1331 } 1332 if (_la(self, 0) == ')') 1333 break; 1334 else if (_la(self, 0) == ' ') 1335 _consume(self, 1); 1336 } 1337 _consumeIfMatch(self, ')'); 1338 return array; 1339} 1340 1341 1342- (BOOL)_parseThreadResponseIntoHashMap:(NGMutableHashMap *)result_ { 1343 NSMutableArray *msn; 1344 1345 if (!_matchesString(self, "THREAD")) 1346 return NO; 1347 1348 _consume(self, 6); 1349 1350 if (_la(self, 0) == ' ') { 1351 _consume(self, 1); 1352 } 1353 1354 msn = [NSMutableArray arrayWithCapacity:64]; 1355 1356 while ((_la(self, 0) == '(')) { 1357 NSArray *array; 1358 1359 if ((array = [self _parseThread]) != nil) 1360 [msn addObject:array]; 1361 } 1362 _parseUntil(self, '\n'); 1363 [result_ addObject:msn forKey:@"thread"]; 1364 return YES; 1365} 1366 1367- (BOOL)_parseVanishedResponseIntoHashMap:(NGMutableHashMap *)result_ { 1368 // VANISHED (EARLIER) 1:53,55:56,58:113,115,120,126,128' 1369 NSMutableArray *uids; 1370 NSNumber *uid; 1371 NSUInteger count, max; 1372 1373 if (!_matchesString(self, "VANISHED")) 1374 return NO; 1375 1376 _consume(self, 8); 1377 1378 if (_la(self, 0) == ' ') { 1379 _consume(self, 1); 1380 } 1381 1382 if (_la(self, 0) == '(') { 1383 _consume(self, 1); 1384 if (!_matchesString(self, "EARLIER")) 1385 return NO; 1386 _consume(self, 7); /* EARLIER */ 1387 _consumeIfMatch(self, ')'); 1388 if (_la(self, 0) == ' ') { 1389 _consume(self, 1); 1390 } 1391 } 1392 1393 uids = [NSMutableArray new]; 1394 1395 while ((_la(self, 0) != '\n')) { 1396 uid = _parseUnsigned(self); 1397 [uids addObject:uid]; 1398 if (_la(self, 0) == ':') { 1399 _consume(self, 1); 1400 count = [uid unsignedIntValue] + 1; 1401 uid = _parseUnsigned(self); 1402 max = [uid unsignedIntValue]; 1403 while (count < max) { 1404 [uids addObject: [NSNumber numberWithUnsignedInt: count]]; 1405 count++; 1406 } 1407 [uids addObject: uid]; 1408 } 1409 if (_la(self, 0) == ',') { 1410 _consume(self, 1); 1411 } 1412 } 1413 _consume(self, 1); 1414 [result_ addObject:uids forKey:@"vanished"]; 1415 [uids release]; 1416 1417 return YES; 1418} 1419 1420- (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ { 1421 NSString *name = nil; 1422 NSMutableDictionary *flags = nil; 1423 NSDictionary *d; 1424 1425 if (!_matchesString(self, "STATUS ")) 1426 return NO; 1427 1428 _consume(self, 7); 1429 1430 if (_la(self, 0) == '"') { 1431 name = [self _parseQuotedString]; 1432// _consume(self, 1); 1433// name = _parseUntil(self, '"'); 1434 _consumeIfMatch(self, ' '); 1435 } 1436 else if (_la(self, 0) == '{') { 1437 name = [self _parseQuotedStringOrNIL]; 1438 _consumeIfMatch(self, ' '); 1439 } 1440 else { 1441 name = _parseUntil(self, ' '); 1442 } 1443 _consumeIfMatch(self, '('); 1444 flags = [NSMutableDictionary dictionaryWithCapacity:8]; 1445 1446 while (_la(self, 0) != ')') { 1447 NSString *key = _parseUntil(self, ' '); 1448 id value = _parseUntil2(self, ' ', ')'); 1449 1450 if (_la(self, 0) == ' ') 1451 _consume(self, 1); 1452 1453 if ([[key lowercaseString] isEqualToString:@"x-guid"]) 1454 [flags setObject:value 1455 forKey:[key lowercaseString]]; 1456 else 1457 [flags setObject:[NumClass numberWithInt:[value intValue]] 1458 forKey:[key lowercaseString]]; 1459 } 1460 _consumeIfMatch(self, ')'); 1461 _parseUntil(self, '\n'); 1462 1463 d = [[NSDictionary alloc] initWithObjectsAndKeys: 1464 name, @"folderName", 1465 flags, @"flags", nil]; 1466 [result_ addObject:d forKey:@"status"]; 1467 [d release]; 1468 return YES; 1469} 1470 1471- (BOOL)_parseAnnotationResponseIntoHashMap:(NGMutableHashMap *)result_ { 1472 NSString *name = nil; 1473 NSString *entry = nil; 1474 NSMutableDictionary *attributes = nil; 1475 NSMutableDictionary *d, *f; 1476 1477 if (!_matchesString(self, "ANNOTATION ")) 1478 return NO; 1479 1480 _consume(self, 11); 1481 1482 if (_la(self, 0) == '"') { 1483 name = [self _parseQuotedString]; 1484 _consumeIfMatch(self, ' '); 1485 } 1486 else if (_la(self, 0) == '{') { 1487 name = [self _parseQuotedStringOrNIL]; 1488 _consumeIfMatch(self, ' '); 1489 } 1490 else { 1491 name = _parseUntil(self, ' '); 1492 } 1493 1494 if (_la(self, 0) == '"') { 1495 entry = [self _parseQuotedString]; 1496 _consumeIfMatch(self, ' '); 1497 } 1498 else if (_la(self, 0) == '{') { 1499 entry = [self _parseQuotedStringOrNIL]; 1500 _consumeIfMatch(self, ' '); 1501 } 1502 else { 1503 entry = _parseUntil(self, ' '); 1504 } 1505 1506 _consumeIfMatch(self, '('); 1507 1508 attributes = [NSMutableDictionary dictionaryWithCapacity:2]; 1509 d = [NSMutableDictionary dictionaryWithCapacity:2]; 1510 f = [NSMutableDictionary dictionaryWithCapacity:2]; 1511 1512 while (_la(self, 0) != ')') { 1513 NSString *key = [self _parseQuotedString]; 1514 _consume(self, 1); // ' ' 1515 NSString *value = [self _parseQuotedStringOrNIL]; 1516 1517 if (_la(self, 0) == ' ') 1518 _consume(self, 1); 1519 1520 if (value) 1521 { 1522 [attributes setObject:value 1523 forKey:[key lowercaseString]]; 1524 1525 [d setObject:[NSMutableDictionary dictionaryWithDictionary:attributes] 1526 forKey:entry]; 1527 } 1528 } 1529 _consumeIfMatch(self, ')'); 1530 _parseUntil(self, '\n'); 1531 1532 [f setObject:d forKey:name]; 1533 [result_ addObject:f forKey:@"FolderList"]; 1534 1535 return YES; 1536} 1537 1538 1539- (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ { 1540 NSString *reason; 1541 1542 if (!_matchesString(self, "BYE ")) 1543 return NO; 1544 1545 _consume(self, 4); 1546 reason = _parseUntil(self, '\n'); 1547 [result_ addObject:reason forKey:@"bye"]; 1548 return YES; 1549} 1550 1551- (id)_decodeQP:(id)_string headerField:(NSString *)_field { 1552 if (![_string isNotNull]) 1553 return _string; 1554 1555 if ([_string isKindOfClass:DataClass]) 1556 return [_string decodeQuotedPrintableValueOfMIMEHeaderField:_field]; 1557 1558 if ([_string isKindOfClass:StrClass]) { 1559 if ([_string length] <= 6 /* minimum size */) 1560 return _string; 1561 1562 if ([_string rangeOfString:@"=?"].length > 0) { 1563 NSData *data; 1564 1565 if (debugOn) 1566 [self debugWithFormat:@"WARNING: string with quoted printable info!"]; 1567 1568 // TODO: this is really expensive ... 1569 data = [_string dataUsingEncoding:NSUTF8StringEncoding]; 1570 if (data != nil) { 1571 NSData *qpData; 1572 1573 qpData = [data decodeQuotedPrintableValueOfMIMEHeaderField:_field]; 1574 if (qpData != data) return qpData; 1575 } 1576 } 1577 return _string; 1578 } 1579 1580 return _string; 1581} 1582 1583- (NGImap4EnvelopeAddress *)_parseEnvelopeAddressStructure { 1584 /* 1585 Note: returns retained object! 1586 1587 Order: 1588 personal name 1589 SMTP@at-domain-list(source route) 1590 mailbox name 1591 hostname 1592 eg: 1593 ("Helge Hess" NIL "helge.hess" "opengroupware.org") 1594 */ 1595 NGImap4EnvelopeAddress *address = nil; 1596 NSString *pname, *route, *mailbox, *host; 1597 1598 if (_la(self, 0) != '(') { 1599 if (_matchesString(self, "NIL")) { 1600 _consume(self, 3); 1601 return (id)[null retain]; 1602 } 1603 return nil; 1604 } 1605 _consume(self, 1); // '(' 1606 1607 /* parse personal name, can be with quoted printable encoding! */ 1608 1609 pname = [self _parseQuotedStringOrNIL]; 1610 if ([pname isNotNull]) // TODO: headerField 'subject'?? explain! 1611 pname = [self _decodeQP:pname headerField:@"subject"]; 1612 [self _consumeOptionalSpace]; 1613 1614 // TODO: I think those forbid QP encoding? 1615 route = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace]; 1616 mailbox = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace]; 1617 host = [self _parseQuotedStringOrNIL]; [self _consumeOptionalSpace]; 1618 1619 if (_la(self, 0) != ')') { 1620 [self logWithFormat:@"WARNING: IMAP4 envelope " 1621 @"address not properly closed (c0=%c,c1=%c): %@", 1622 _la(self, 0), _la(self, 1), self->serverResponseDebug]; 1623 } 1624 else 1625 _consume(self, 1); 1626 1627 if ([pname isNotNull] || [route isNotNull] || [mailbox isNotNull] || [host isNotNull]) 1628 address = [[NGImap4EnvelopeAddress alloc] initWithPersonalName:pname 1629 sourceRoute:route 1630 mailbox:mailbox 1631 host:host]; 1632 1633 return address; 1634} 1635 1636- (NSArray *)_parseEnvelopeAddressStructures { 1637 /* 1638 Parses an array of envelopes, most common: 1639 ((NIL NIL "users-admin" "opengroupware.org")) 1640 (just one envelope in the array) 1641 */ 1642 NSMutableArray *ma; 1643 1644 if (_la(self, 0) != '(') { 1645 if (_matchesString(self, "NIL")) { 1646 _consume(self, 3); 1647 return (id)[null retain]; 1648 } 1649 return nil; 1650 } 1651 _consume(self, 1); // '(' 1652 1653 ma = nil; 1654 while (_la(self, 0) != ')') { 1655 NGImap4EnvelopeAddress *address; 1656 1657 if ((address = [self _parseEnvelopeAddressStructure]) == nil) { 1658 [self _consumeOptionalSpace]; 1659 continue; // TODO: should we stop parsing? 1660 } 1661 if (![address isNotNull]) 1662 continue; 1663 1664 if (ma == nil) ma = [NSMutableArray arrayWithCapacity:4]; 1665 [ma addObject:address]; 1666 [address release]; /* the parse returns a retained object! */ 1667 } 1668 1669 if (_la(self, 0) != ')') { 1670 [self logWithFormat: 1671 @"WARNING: IMAP4 envelope address not properly closed!"]; 1672 } 1673 else 1674 _consume(self, 1); 1675 return ma; 1676} 1677 1678- (id)_parseEnvelope { 1679 /* 1680 http://www.hunnysoft.com/rfc/rfc3501.html 1681 1682 envelope = "(" env-date SP env-subject SP env-from SP env-sender SP 1683 env-reply-to SP env-to SP env-cc SP env-bcc SP 1684 env-in-reply-to SP env-message-id ")" 1685 1686 * 1189 FETCH (UID 1189 ENVELOPE 1687 ("Tue, 22 Jun 2004 08:42:01 -0500" "" 1688 (("Jeff Glaspie" NIL "jeff" "glaspie.org")) 1689 (("Jeff Glaspie" NIL "jeff" "glaspie.org")) 1690 (("Jeff Glaspie" NIL "jeff" "glaspie.org")) 1691 ((NIL NIL "helge.hess" "opengroupware.org")) 1692 NIL NIL NIL 1693 "<20040622134354.F11133CEB14@mail.opengroupware.org>" 1694 ) 1695 ) 1696 */ 1697 static NGMimeRFC822DateHeaderFieldParser *dateParser = nil; 1698 NGImap4Envelope *env; 1699 NSString *dateStr; 1700 id tmp; 1701 1702 if (dateParser == nil) 1703 dateParser = [[NGMimeRFC822DateHeaderFieldParser alloc] init]; 1704 1705 if (_la(self, 0) != '(') 1706 return nil; 1707 _consume(self, 1); 1708 1709 env = [[[NGImap4Envelope alloc] init] autorelease]; 1710 1711 /* parse date */ 1712 1713 dateStr = [self _parseQuotedStringOrNIL]; 1714 [self _consumeOptionalSpace]; 1715 if ([dateStr isNotNull]) 1716 env->date = [[dateParser parseValue:dateStr ofHeaderField:nil] retain]; 1717 1718 /* parse subject */ 1719 1720 if ((tmp = [self _parseQuotedStringOrDataOrNIL]) != nil) { 1721 // TODO: that one is an issue, the client does know the requested charset 1722 // but doesn't pass it down to the parser? Requiring the client to 1723 // deal with NSData's is a bit overkill? 1724 env->subject = [tmp isNotNull] 1725 ? [[self _decodeQP:tmp headerField:@"subject"] copy] 1726 : nil; 1727 [self _consumeOptionalSpace]; 1728 } 1729 else { 1730 [self logWithFormat:@"ERROR(%s): failed on subject(%c): %@", 1731 __PRETTY_FUNCTION__, _la(self, 0), self->serverResponseDebug]; 1732 return nil; 1733 } 1734 1735 /* parse addresses */ 1736 1737 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) { 1738 env->from = [tmp isNotNull] ? [tmp copy] : nil; 1739 [self _consumeOptionalSpace]; 1740 } 1741 else { 1742 [self logWithFormat:@"ERROR(%s): failed on from.", __PRETTY_FUNCTION__]; 1743 return nil; 1744 } 1745 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) { 1746 env->sender = [tmp isNotNull] ? [[tmp lastObject] copy] : nil; 1747 [self _consumeOptionalSpace]; 1748 } 1749 else { 1750 [self logWithFormat:@"ERROR(%s): failed on sender.", __PRETTY_FUNCTION__]; 1751 return nil; 1752 } 1753 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) { 1754 env->replyTo = [tmp isNotNull] ? [tmp copy] : nil; 1755 [self _consumeOptionalSpace]; 1756 } 1757 1758 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) { 1759 env->to = [tmp isNotNull] ? [tmp copy] : nil; 1760 [self _consumeOptionalSpace]; 1761 } 1762 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) { 1763 env->cc = [tmp isNotNull] ? [tmp copy] : nil; 1764 [self _consumeOptionalSpace]; 1765 } 1766 if ((tmp = [self _parseEnvelopeAddressStructures]) != nil) { 1767 env->bcc = [tmp isNotNull] ? [tmp copy] : nil; 1768 [self _consumeOptionalSpace]; 1769 } 1770 1771 if ((tmp = [self _parseQuotedStringOrNIL])) { 1772 env->inReplyTo = [tmp isNotNull] ? [tmp copy] : nil; 1773 [self _consumeOptionalSpace]; 1774 } 1775 if ((tmp = [self _parseQuotedStringOrNIL])) { 1776 env->msgId = [tmp isNotNull] ? [tmp copy] : nil; 1777 [self _consumeOptionalSpace]; 1778 } 1779 1780 if (_la(self, 0) != ')') { 1781 [self logWithFormat:@"WARNING: IMAP4 envelope not properly closed" 1782 @" (c0=%c,c1=%c): %@", 1783 _la(self, 0), _la(self, 1), self->serverResponseDebug]; 1784 } 1785 else 1786 _consume(self, 1); 1787 1788 return env; 1789} 1790 1791- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ { 1792 NSMutableDictionary *fetch; 1793 NSNumber *number; 1794 NSString *key = nil; 1795 1796 if ((number = _parseUnsigned(self)) == nil) 1797 return NO; 1798 1799 _consumeIfMatch(self, ' '); 1800 1801 if (!_matchesString(self, "FETCH ")) { 1802 /* got a number request from select like exists or recent */ 1803 key = _parseUntil(self, '\n'); 1804 [result_ addObject:number forKey:[key lowercaseString]]; 1805 return YES; 1806 } 1807 1808 /* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */ 1809 fetch = [[NSMutableDictionary alloc] initWithCapacity:10]; 1810 1811 _consume(self, 6); /* "FETCH " */ 1812 _consumeIfMatch(self, '('); 1813 while (_la(self, 0) != ')') { /* until closing parent */ 1814 NSString *key; 1815 1816 key = [_parseUntil(self, ' ') lowercaseString]; 1817#if 0 1818 [self logWithFormat:@"PARSE KEY: %@", key]; 1819#endif 1820 if ([key hasPrefix:@"body[header.fields"]) { 1821 NSData *content; 1822 1823 if ((content = [self _parseBodyHeaderFields]) != nil) 1824 [fetch setObject:content forKey:key]; 1825 else 1826 [self logWithFormat:@"ERROR: got no body content for key: '%@'",key]; 1827 } 1828 else if ([key hasPrefix:@"body["]) { 1829 NSDictionary *content; 1830 1831 if ((content = [self _parseBodyContent]) != nil) 1832 [fetch setObject:content forKey:key]; 1833 else 1834 [self logWithFormat:@"ERROR: got no body content for key: '%@'",key]; 1835 } 1836 else if ([key isEqualToString:@"body"]) { 1837 [fetch setObject:_parseBody(self, NO) forKey:key]; 1838 } 1839 else if ([key isEqualToString:@"bodystructure"]) { 1840 [fetch setObject:_parseBody(self, YES) forKey:key]; 1841 } 1842 else if ([key isEqualToString:@"flags"]) { 1843 [fetch setObject:_parseFlagArray(self) forKey:key]; 1844 } 1845 else if ([key isEqualToString:@"uid"]) { 1846 [fetch setObject:_parseUnsigned(self) forKey:key]; 1847 } 1848 else if ([key isEqualToString:@"modseq"]) { 1849 _consumeIfMatch(self, '('); 1850 [fetch setObject:_parseUnsigned(self) forKey:key]; 1851 _consumeIfMatch(self, ')'); 1852 } 1853 else if ([key isEqualToString:@"rfc822.size"]) { 1854 [fetch setObject:_parseUnsigned(self) forKey:key]; 1855 } 1856 else if ([key hasPrefix:@"rfc822"]) { 1857 NSData *data; 1858 1859 if (_la(self, 0) == '"') { 1860 NSString *str; 1861 _consume(self,1); 1862 1863 str = _parseUntil(self, '"'); 1864 data = [str dataUsingEncoding:defCStringEncoding]; 1865 } 1866 else 1867 data = [self _parseData]; 1868 1869 if (data != nil) [fetch setObject:data forKey:key]; 1870 } 1871 else if ([key isEqualToString:@"envelope"]) { 1872 id envelope; 1873 1874 if ((envelope = [self _parseEnvelope]) != nil) 1875 [fetch setObject:envelope forKey:key]; 1876 else 1877 [self logWithFormat:@"ERROR: could not parse envelope!"]; 1878 } 1879 else if ([key isEqualToString:@"internaldate"]) { 1880 // TODO: implement! 1881 NSException *e; 1882 1883 e = [[NGImap4ParserException alloc] 1884 initWithFormat:@"INTERNALDATE fetch result not yet supported!"]; 1885 [self setLastException:[e autorelease]]; 1886 return NO; 1887 } 1888 else { 1889 NSException *e; 1890 1891 e = [[NGImap4ParserException alloc] initWithFormat: 1892 @"unsupported fetch key: %@", 1893 key]; 1894 [self setLastException:[e autorelease]]; 1895 return NO; 1896 } 1897 1898 if (_la(self, 0) == ' ') 1899 _consume(self, 1); 1900 } 1901 if (fetch != nil) { 1902 [fetch setObject:number forKey:@"msn"]; 1903 [result_ addObject:fetch forKey:@"fetch"]; 1904 _consume(self, 1); /* consume ')' */ 1905 _consumeIfMatch(self, '\n'); 1906 } 1907 else { /* no correct fetch line */ 1908 _parseUntil(self, '\n'); 1909 } 1910 1911 [fetch release]; fetch = nil; 1912 return YES; 1913 } 1914 1915static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self, 1916 NGMutableHashMap *result_) 1917{ 1918 BOOL isOK; 1919 1920 while (!(isOK = _parseOkSieveResponse(self, result_))) { 1921 NSString *key, *value; 1922 1923 if (!(key = _parseStringSieveResponse(self))) { 1924 break; 1925 } 1926 if (_la(self, 0) == ' ') { 1927 _consume(self, 1); 1928 1929 if (!(value = _parseStringSieveResponse(self))) { 1930 break; 1931 } 1932 } 1933 else { 1934 value = @""; 1935 } 1936 _parseUntil(self, '\n'); 1937 [result_ addObject:value forKey:[key lowercaseString]]; 1938 } 1939 1940 return isOK; 1941} 1942 1943static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self, 1944 NGMutableHashMap *result_) 1945{ 1946 NSString *str; 1947 NSData *data; 1948 1949 if ((data = [self _parseData]) == nil) 1950 return NO; 1951 1952 str = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding]; 1953 [result_ setObject:str forKey:@"data"]; 1954 [str release]; str = nil; 1955 1956 _parseUntil(self, '\n'); 1957 1958 return YES; 1959} 1960 1961static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self, 1962 NGMutableHashMap *result_) 1963{ 1964 if (!((_la(self, 0) == 'O') && (_la(self, 1) == 'K'))) 1965 return NO; 1966 1967 _consume(self, 2); 1968 1969 if (_la(self, 0) == ' ') { 1970 NSString *reason; 1971 1972 if ((reason = _parseContentSieveResponse(self))) 1973 [result_ addObject:reason forKey:@"reason"]; 1974 } 1975 _parseUntil(self, '\n'); 1976 1977 [result_ addObject:YesNum forKey:@"ok"]; 1978 return YES; 1979} 1980 1981static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self, 1982 NGMutableHashMap *result_) 1983{ 1984 NSString *code, *data; 1985 1986 if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' '))) 1987 return NO; 1988 1989 _consume(self, 3); 1990 1991 if (_la(self, 0) == '(') 1992 { 1993 _consume(self, 1); 1994 code = _parseUntil(self, ')'); 1995 if (_la(self, 0) == ' ') _consume(self, 1); 1996 if (code) [result_ addObject:code forKey:@"code"]; 1997 } 1998 1999 data = _parseContentSieveResponse(self); 2000 2001 [result_ addObject:NoNum forKey:@"ok"]; 2002 if (data) [result_ addObject:data forKey:@"reason"]; 2003 2004 _parseUntil(self, '\n'); 2005 2006 return YES; 2007} 2008 2009static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) { 2010 NSString *str; 2011 NSData *data; 2012 2013 if ((str = _parseStringSieveResponse(self))) 2014 return str; 2015 2016 if ((data = [self _parseData]) == nil) 2017 return nil; 2018 2019 return [[[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding] 2020 autorelease]; 2021} 2022 2023static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self) { 2024 if (_la(self, 0) != '"') 2025 return nil; 2026 2027 _consume(self, 1); 2028 return _parseUntil(self, '"'); 2029} 2030 2031static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self, 2032 BOOL _convertString, 2033 BOOL _decode) 2034{ 2035 NSString *str; 2036 2037 if (_la(self, 0) == '"') { 2038 // TODO: can the " be escaped somehow? 2039 _consume(self, 1); 2040 str = _parseUntil(self, '"'); 2041 } 2042 else if (_la(self, 0) == '{') { 2043 NSData *data; 2044 2045 if (debugDataOn) [self logWithFormat:@"parse body decode string"]; 2046 data = [self _parseData]; 2047 2048 if (_decode) 2049 data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil]; 2050 2051 if ([data isKindOfClass: [NSString class]]) 2052 return (NSString *) data; 2053 else 2054 { 2055 NSString *s; 2056 2057 // Let's try with the supplied encoding. If it doesn't work, 2058 // we'll then try UTF-8 or fallback to ISO-8859-1 2059 s = [[StrClass alloc] initWithData:data encoding:encoding]; 2060 2061 if (!s && encoding != NSUTF8StringEncoding) 2062 s = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding]; 2063 2064 if (!s) 2065 s = [[StrClass alloc] initWithData:data encoding:NSISOLatin1StringEncoding]; 2066 2067 return [s autorelease]; 2068 } 2069 } 2070 else { 2071 str = _parseUntil2(self, ' ', ')'); 2072 } 2073 if (_convertString) { 2074 if ([[str lowercaseString] isEqualToString:@"nil"]) 2075 str = @""; 2076 } 2077 if (_decode) { 2078 id d; 2079 2080 d = [str dataUsingEncoding:defCStringEncoding]; 2081 d = [d decodeQuotedPrintableValueOfMIMEHeaderField:nil]; 2082 2083 if ([d isKindOfClass:StrClass]) 2084 str = d; 2085 else { 2086 str = [[[StrClass alloc] initWithData:d encoding:encoding] 2087 autorelease]; 2088 } 2089 } 2090 return str; 2091} 2092 2093static NSString *_parseBodyString(NGImap4ResponseParser *self, 2094 BOOL _convertString) 2095{ 2096 return _parseBodyDecodeString(self, _convertString, NO /* no decode */); 2097} 2098 2099static NSArray *_parseLanguages(NGImap4ResponseParser *self) { 2100 NSMutableArray *languages; 2101 NSString *language; 2102 2103 languages = [NSMutableArray array]; 2104 if (_la(self, 0) == '(') { 2105 while (_la(self, 0) != ')') { 2106 _consume(self,1); 2107 language = _parseBodyString(self, YES); 2108 if ([language length]) 2109 [languages addObject: language]; 2110 } 2111 _consume(self,1); 2112 } 2113 else { 2114 language = _parseBodyString(self, YES); 2115 if ([language length]) 2116 [languages addObject: language]; 2117 } 2118 2119 return languages; 2120} 2121 2122static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self) 2123{ 2124 NSMutableDictionary *list; 2125 2126 if (_la(self, 0) == '(') { 2127 _consume(self, 1); 2128 2129 list = [NSMutableDictionary dictionaryWithCapacity:4]; 2130 2131 while (_la(self,0) != ')') { 2132 NSString *key, *value; 2133 2134 if (_la(self, 0) == ' ') 2135 _consume(self, 1); 2136 2137 key = _parseBodyString(self, YES); 2138 _consumeIfMatch(self, ' '); 2139 value = _parseBodyDecodeString(self, YES, YES); 2140 2141 if (value) [list setObject:value forKey:[key lowercaseString]]; 2142 } 2143 _consumeIfMatch(self, ')'); 2144 } 2145 else { 2146 NSString *str; 2147 str = _parseBodyString(self, YES); 2148 2149 if ([str isNotEmpty]) 2150 NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str); 2151 2152 list = (id)[NSDictionary dictionary]; 2153 } 2154 return list; 2155} 2156 2157static NSDictionary *_parseContentDisposition(NGImap4ResponseParser *self) 2158{ 2159 NSMutableDictionary *disposition; 2160 NSString *type; 2161 2162 disposition = [NSMutableDictionary dictionary]; 2163 2164 if (_la(self, 0) == '(') { 2165 _consume(self, 1); 2166 type = _parseBodyString(self, YES); 2167 [disposition setObject: type forKey: @"type"]; 2168 if (_la(self, 0) != ')') { 2169 _consume(self, 1); 2170 [disposition setObject: _parseBodyParameterList(self) 2171 forKey: @"parameterList"]; 2172 } 2173 _consume(self, 1); 2174 } 2175 else 2176 _parseBodyString(self, YES); 2177 2178 return disposition; 2179} 2180 2181static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) { 2182 NSString *personalName, *sourceRoute, *mailboxName, *hostName; 2183 2184 // Handle broken address list - generally sent by Microsoft Exchange. 2185 if (_la(self, 0) == '(') { 2186 _consume(self, 1); 2187 2188 personalName = _parseBodyString(self, YES); 2189 _consumeIfMatch(self, ' '); 2190 sourceRoute = _parseBodyString(self, YES); 2191 _consumeIfMatch(self, ' '); 2192 mailboxName = _parseBodyString(self, YES); 2193 _consumeIfMatch(self, ' '); 2194 hostName = _parseBodyString(self, YES); 2195 _consumeIfMatch(self, ')'); 2196 2197 return [NSDictionary dictionaryWithObjectsAndKeys: 2198 personalName, @"personalName", 2199 sourceRoute, @"sourceRoute", 2200 mailboxName, @"mailboxName", 2201 hostName, @"hostName", nil]; 2202 } 2203 2204 _parseUntil(self, ')'); 2205 2206 return [NSDictionary dictionary]; 2207} 2208 2209static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self) { 2210 NSMutableArray *result; 2211 result = [NSMutableArray arrayWithCapacity:8]; 2212 2213 if (_la(self, 0) == '(') { 2214 _consume(self, 1); 2215 while (_la(self, 0) != ')') { 2216 [result addObject:_parseAddressStructure(self)]; 2217 } 2218 _consume(self, 1); 2219 } 2220 else { 2221 NSString *str; 2222 str = _parseBodyString(self, YES); 2223 2224 if ([str isNotEmpty]) 2225 NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str); 2226 2227 result = (id)[NSArray array]; 2228 } 2229 return result; 2230} 2231 2232static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self, 2233 BOOL isBodyStructure) { 2234 NSString *type, *subtype, *bodyId, *description, 2235 *result, *encoding, *bodysize; 2236 NSDictionary *parameterList; 2237 NSMutableDictionary *dict; 2238 NSArray *languages; 2239 2240 type = [_parseBodyString(self, YES) lowercaseString]; 2241 _consumeIfMatch(self, ' '); 2242 subtype = [_parseBodyString(self, YES) lowercaseString]; 2243 _consumeIfMatch(self, ' '); 2244 parameterList = _parseBodyParameterList(self); 2245 _consumeIfMatch(self, ' '); 2246 bodyId = _parseBodyString(self, YES); 2247 _consumeIfMatch(self, ' '); 2248 description = _parseBodyString(self, YES); 2249 _consumeIfMatch(self, ' '); 2250 encoding = _parseBodyString(self, YES); 2251 _consumeIfMatch(self, ' '); 2252 bodysize = _parseBodyString(self, YES); 2253 2254 dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: 2255 type, @"type", 2256 subtype, @"subtype", 2257 parameterList, @"parameterList", 2258 bodyId, @"bodyId", 2259 description, @"description", 2260 encoding, @"encoding", 2261 bodysize, @"size", nil]; 2262 2263 if ([type isEqualToString:@"text"]) { 2264 _consumeIfMatch(self, ' '); 2265 [dict setObject:_parseBodyString(self, YES) forKey:@"lines"]; 2266 } 2267 else if ([type isEqualToString:@"message"] 2268 && [subtype isEqualToString:@"rfc822"]) { 2269 if (_la(self, 0) != ')') { 2270 _consumeIfMatch(self, ' '); 2271 _consumeIfMatch(self, '('); 2272 result = _parseBodyString(self, YES); 2273 if (result == nil) result = @""; 2274 [dict setObject:result forKey:@"date"]; 2275 _consumeIfMatch(self, ' '); 2276 result = _parseBodyString(self, YES); 2277 if (result == nil) result = @""; 2278 [dict setObject:result forKey:@"subject"]; 2279 _consumeIfMatch(self, ' '); 2280 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"]; 2281 if (_la(self, 0) == ' ') 2282 _consume(self, 1); 2283 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"sender"]; 2284 if (_la(self, 0) == ' ') 2285 _consume(self, 1); 2286 [dict setObject:_parseParenthesizedAddressList(self) 2287 forKey:@"reply-to"]; 2288 if (_la(self, 0) == ' ') 2289 _consume(self, 1); 2290 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"to"]; 2291 if (_la(self, 0) == ' ') 2292 _consume(self, 1); 2293 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"cc"]; 2294 if (_la(self, 0) == ' ') 2295 _consume(self, 1); 2296 [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"]; 2297 if (_la(self, 0) == ' ') 2298 _consume(self, 1); 2299 result = _parseBodyString(self, YES); 2300 if (result == nil) result = @""; 2301 [dict setObject:result forKey:@"in-reply-to"]; 2302 if (_la(self, 0) == ' ') 2303 _consume(self, 1); 2304 result = _parseBodyString(self, YES); 2305 if (result == nil) result = @""; 2306 [dict setObject:result forKey:@"messageId"]; 2307 _consumeIfMatch(self, ')'); 2308 if (_la(self, 0) == ' ') 2309 _consume(self, 1); 2310 [dict setObject:_parseBody(self, isBodyStructure) forKey:@"body"]; 2311 if (_la(self, 0) == ' ') 2312 _consume(self, 1); 2313 result = _parseBodyString(self, YES); 2314 if (result == nil) result = @""; 2315 [dict setObject:result forKey:@"bodyLines"]; 2316 } 2317 } 2318 2319 if (isBodyStructure) { 2320 if (_la(self, 0) != ')') { 2321 _consume(self,1); 2322 [dict setObject: _parseBodyString(self, YES) 2323 forKey: @"md5"]; 2324 if (_la(self, 0) != ')') { 2325 _consume(self,1); 2326 [dict setObject: _parseContentDisposition(self) 2327 forKey: @"disposition"]; 2328 if (_la(self, 0) != ')') { 2329 _consume(self,1); 2330 languages = _parseLanguages(self); 2331 if ([languages count]) 2332 [dict setObject: languages forKey: @"languages"]; 2333 if (_la(self, 0) != ')') { 2334 _consume(self,1); 2335 [dict setObject: _parseBodyString(self, YES) 2336 forKey: @"location"]; 2337 }; 2338 }; 2339 }; 2340 }; 2341 } 2342 2343 return dict; 2344} 2345 2346static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self, 2347 BOOL isBodyStructure) { 2348 NSMutableArray *parts; 2349 NSArray *languages; 2350 NSString *kind; 2351 NSMutableDictionary *dict; 2352 2353 parts = [NSMutableArray arrayWithCapacity:4]; 2354 2355 while (_la(self, 0) == '(') { 2356 [parts addObject:_parseBody(self, isBodyStructure)]; 2357 } 2358 _consumeIfMatch(self, ' '); 2359 kind = _parseBodyString(self, YES); 2360 dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: 2361 parts, @"parts", 2362 @"multipart", @"type", 2363 kind , @"subtype", nil]; 2364 if (isBodyStructure) { 2365 if (_la(self, 0) != ')') { 2366 _consume(self,1); 2367 [dict setObject: _parseBodyParameterList(self) 2368 forKey: @"parameterList"]; 2369 if (_la(self, 0) != ')') { 2370 _consume(self,1); 2371 [dict setObject: _parseContentDisposition(self) 2372 forKey: @"disposition"]; 2373 if (_la(self, 0) != ')') { 2374 _consume(self,1); 2375 languages = _parseLanguages(self); 2376 if ([languages count]) 2377 [dict setObject: languages forKey: @"languages"]; 2378 if (_la(self, 0) != ')') { 2379 _consume(self,1); 2380 [dict setObject: _parseBodyString(self, YES) 2381 forKey: @"location"]; 2382 }; 2383 }; 2384 }; 2385 }; 2386 } 2387 2388 return dict; 2389} 2390 2391static NSDictionary *_parseBody(NGImap4ResponseParser *self, BOOL isBodyStructure) { 2392 NSDictionary *result; 2393 2394 _consumeIfMatch(self, '('); 2395 2396 if (_la(self, 0) == '(') { 2397 result = _parseMultipartBody(self, isBodyStructure); 2398 } 2399 else { 2400 result = _parseSingleBody(self, isBodyStructure); 2401 } 2402 if (_la(self,0) != ')') { 2403 NSString *str; 2404 2405 str = _parseUntil(self, ')'); 2406 NSLog(@"%s: got noparsed content %@", __PRETTY_FUNCTION__, 2407 str); 2408 } 2409 else 2410 _consume(self, 1); 2411 2412 return result; 2413} 2414 2415- (NSDictionary *)_parseBodyContent { 2416 NSData *data; 2417 unsigned char c0; 2418 2419 c0 = _la(self, 0); 2420 if (c0 == '"') { 2421 NSString *str; 2422 _consume(self,1); 2423 2424 str = _parseUntil(self, '"'); 2425 data = [str dataUsingEncoding:defCStringEncoding]; 2426 } 2427 else if (c0 == 'N' && _matchesString(self, "NIL")) { 2428 _consume(self, 3); 2429 return nil; 2430 } 2431 else 2432 data = [self _parseData]; 2433 2434 if (data == nil) { 2435 [self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__]; 2436 return nil; 2437 } 2438 return [NSDictionary dictionaryWithObject:data forKey:@"data"]; 2439} 2440 2441 2442static NSArray *_parseFlagArray(NGImap4ResponseParser *self) { 2443 NSString *flags; 2444 2445 _consumeIfMatch(self, '('); 2446 2447 flags = _parseUntil(self, ')'); 2448 if (![flags isNotEmpty]) { 2449 static NSArray *emptyArray = nil; 2450 if (emptyArray == nil) emptyArray = [[NSArray alloc] init]; 2451 return emptyArray; 2452 } 2453 else 2454 return [[flags lowercaseString] componentsSeparatedByString:@" "]; 2455} 2456 2457static BOOL _parseEnabledUntaggedResponse(NGImap4ResponseParser *self, 2458 NGMutableHashMap *result_) { 2459 NSMutableArray *extensions; 2460 NSString *extension; 2461 2462 if ((_la(self, 0) == 'E') 2463 && (_la(self, 1) == 'N') 2464 && (_la(self, 2) == 'A') 2465 && (_la(self, 3) == 'B') 2466 && (_la(self, 4) == 'L') 2467 && (_la(self, 5) == 'E') 2468 && (_la(self, 6) == 'D')) { 2469 extensions = [NSMutableArray new]; 2470 [result_ setObject: extensions forKey: @"extensions"]; 2471 [extensions release]; 2472 2473 _consume(self, 7); 2474 while (_la(self, 0) == ' ') { 2475 _consume(self, 1); 2476 extension = _parseUntil2(self, ' ', '\n'); 2477 if ([extension length] > 0) { 2478 [extensions addObject: extension]; 2479 } 2480 } 2481 _consumeIfMatch(self, '\n'); 2482 return YES; 2483 } 2484 return NO; 2485} 2486 2487static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self, 2488 NGMutableHashMap *result_) { 2489 if ((_la(self, 0) == 'F') 2490 && (_la(self, 1) == 'L') 2491 && (_la(self, 2) == 'A') 2492 && (_la(self, 3) == 'G') 2493 && (_la(self, 4) == 'S') 2494 && (_la(self, 5) == ' ')) { 2495 _consume(self, 6); 2496 [result_ addObject:_parseFlagArray(self) forKey:@"flags"]; 2497 _consumeIfMatch(self, '\n'); 2498 return YES; 2499 } 2500 return NO; 2501} 2502 2503static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self, 2504 NGMutableHashMap *result_) 2505{ 2506 if (!((_la(self, 0)=='B') && (_la(self, 1)=='A') && (_la(self, 2)=='D') 2507 && (_la(self, 3) == ' '))) 2508 return NO; 2509 2510 _consume(self, 4); 2511 [result_ addObject:_parseUntil(self, '\n') forKey:@"bad"]; 2512 return YES; 2513} 2514 2515static BOOL _parseNoOrOkArguments(NGImap4ResponseParser *self, 2516 NGMutableHashMap *result_, NSString *_key) 2517{ 2518 id obj; 2519 2520 obj = nil; 2521 2522 if (_la(self, 0) == '[') { 2523 NSString *key; 2524 2525 _consume(self, 1); 2526 key = _parseUntil2(self, ']', ' '); 2527 2528 /* possible kinds of untagged OK responses are either 2529 * OK [ALERT] System shutdown in 10 minutes 2530 or 2531 * OK [UNSEEN 14] 2532 or 2533 * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)] 2534 */ 2535 if (_la(self, 0) == ']') { 2536 2537 _consume(self, 1); 2538 if (_la(self, 0) == ' ') { 2539 id value; 2540 2541 _consume(self, 1); 2542 value = _parseUntil(self, '\n'); 2543 if ([value isNotEmpty]) { 2544 obj = [[NSDictionary alloc] 2545 initWithObjects:&value forKeys:&key count:1]; 2546 } 2547 else 2548 obj = [key retain]; 2549 } 2550 else { 2551 obj = [key retain]; 2552 _parseUntil(self, '\n'); 2553 } 2554 } 2555 else { /* _la(self, 0) should be ' ' */ 2556 id value; 2557 2558 value = nil; 2559 2560 _consume(self, 1); 2561 if (_la(self, 0) == '(') { 2562 value = _parseFlagArray(self); 2563 _consume(self, 1); /* consume ']' */ 2564 } 2565 else { 2566 value = _parseUntil(self, ']'); 2567 } 2568 { 2569 id tmp; 2570 2571 tmp = _parseUntil(self, '\n'); 2572 2573 obj = [[NSDictionary alloc] initWithObjectsAndKeys: 2574 value, key, 2575 tmp, @"comment", nil]; 2576 } 2577 } 2578 } 2579 else 2580 obj = [_parseUntil(self, '\n') retain]; 2581 2582 [result_ addObject:obj forKey:_key]; 2583 [obj release]; 2584 return YES; 2585} 2586 2587 2588static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self, 2589 NGMutableHashMap *result_) 2590{ 2591 if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' '))) 2592 return NO; 2593 2594 _consume(self, 3); 2595 return _parseNoOrOkArguments(self, result_, @"no"); 2596} 2597 2598static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self, 2599 NGMutableHashMap *result_) 2600{ 2601 if (!((_la(self, 0)=='O') && (_la(self, 1)=='K') && (_la(self, 2)==' '))) 2602 return NO; 2603 2604 _consume(self, 3); 2605 return _parseNoOrOkArguments(self, result_, @"ok"); 2606} 2607 2608static NSNumber *_parseUnsigned(NGImap4ResponseParser *self) { 2609 unsigned n; 2610 unsigned char c; 2611 BOOL isNumber; 2612 2613 isNumber = NO; 2614 n = 0; 2615 c = _la(self, 0); 2616 2617 while ((c >= '0') && (c <= '9')) { 2618 _consume(self, 1); 2619 isNumber = YES; 2620 n = 10 * n + (c - 48); 2621 c = _la(self, 0); 2622 } 2623 if (!isNumber) 2624 return nil; 2625 2626 return [NumClass numberWithUnsignedInt:n]; 2627} 2628 2629static NSString *_parseUntil(NGImap4ResponseParser *self, char _c) { 2630 /* 2631 Note: this function consumes the stop char (_c)! 2632 normalize \r\n constructions 2633 */ 2634 // TODO: optimize! 2635 char buf[1024], c; 2636 NSMutableString *str; 2637 unsigned cnt; 2638 2639 cnt = 0; 2640 str = nil; 2641 while ((c = _la(self, 0)) != _c) { 2642 if (c == '\\') { 2643 _consume(self, 1); 2644 c = _la(self, 0); 2645 } 2646 buf[cnt] = c; 2647 _consume(self, 1); 2648 cnt++; 2649 if (cnt == 1024) { 2650 if (str == nil) { 2651 str = (NSMutableString *) 2652 [NSMutableString stringWithCString:buf length:1024]; 2653 } 2654 else { 2655 NSString *s; 2656 2657 s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024]; 2658 [str appendString:s]; 2659 [s release]; 2660 } 2661 cnt = 0; 2662 } 2663 } 2664 _consume(self,1); /* consume known stop char */ 2665 if (_c == '\n' && cnt > 0) { 2666 if (buf[cnt-1] == '\r') 2667 cnt--; 2668 } 2669 2670 if (str == nil) 2671 return [StrClass stringWithCString:buf length:cnt]; 2672 else { 2673 NSString *s, *s2; 2674 2675 s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt]; 2676 s2 = [str stringByAppendingString:s]; 2677 [s release]; 2678 return s2; 2679 } 2680} 2681 2682static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){ 2683 /* _parseUntil2(self, char, char) doesn`t consume the stop-chars */ 2684 char buf[1024], c; 2685 NSMutableString *str; 2686 unsigned cnt; 2687 2688 cnt = 0; 2689 c = _la(self, 0); 2690 str = nil; 2691 2692 while ((c != _c1) && (c != _c2)) { 2693 buf[cnt] = c; 2694 _consume(self, 1); 2695 cnt++; 2696 if (cnt == 1024) { 2697 if (str == nil) 2698 str = (NSMutableString *) 2699 [NSMutableString stringWithCString:buf length:1024]; 2700 else { 2701 NSString *s; 2702 2703 s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024]; 2704 [str appendString:s]; 2705 [s release]; 2706 } 2707 2708 cnt = 0; 2709 } 2710 c = _la(self, 0); 2711 } 2712 2713 if (str == nil) 2714 return [StrClass stringWithCString:buf length:cnt]; 2715 2716 { 2717 NSString *s, *s2; 2718 2719 s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt]; 2720 s2 = [str stringByAppendingString:s]; 2721 [s release]; 2722 return s2; 2723 } 2724} 2725 2726- (NSException *)exceptionForFailedMatch:(unsigned char)_match 2727 got:(unsigned char)_avail 2728{ 2729 NSException *e; 2730 2731 e = [NGImap4ParserException alloc]; 2732 if (self->debug) { 2733 e = [e initWithFormat:@"unexpected char <%c> expected <%c> <%@>", 2734 _avail, _match, self->serverResponseDebug]; 2735 } 2736 else { 2737 e = [e initWithFormat:@"unexpected char <%c> expected <%c>", 2738 _avail, _match]; 2739 } 2740 return [e autorelease]; 2741} 2742 2743static __inline__ NSException *_consumeIfMatch(NGImap4ResponseParser *self, 2744 unsigned char _match) 2745{ 2746 NSException *e; 2747 2748 if (_la(self,0) == _match) { 2749 _consume(self, 1); 2750 return nil; 2751 } 2752 2753 e = [self exceptionForFailedMatch:_match got:_la(self, 0)]; 2754 [self setLastException:e]; 2755 return e; 2756} 2757 2758static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) { 2759 /* Normalize end of line */ 2760 2761 if (_cnt == 0) 2762 return; 2763 2764 _cnt += (__la(self, _cnt - 1) == '\r') ? 1 : 0; 2765 2766 if (self->debug) { 2767 unsigned cnt; 2768 2769 for (cnt = 0; cnt < _cnt; cnt++) { 2770 NSString *s; 2771 unichar c = _la(self, cnt); 2772 2773 if (c == '\r') 2774 continue; 2775 2776 s = [[StrClass alloc] initWithCharacters:&c length:1]; 2777 [self->serverResponseDebug appendString:s]; 2778 [s release]; 2779 2780 if (c == '\n') { 2781 if ([self->serverResponseDebug lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding] > 2) { 2782 fprintf(stderr, "S[%p]: %s", self, 2783 [self->serverResponseDebug cStringUsingEncoding:NSISOLatin1StringEncoding]); 2784 } 2785 [self->serverResponseDebug release]; 2786 self->serverResponseDebug = 2787 [[NSMutableString alloc] initWithCapacity:512]; 2788 } 2789 } 2790 } 2791 [self->buffer consume:_cnt]; 2792} 2793 2794@end /* NGImap4ResponseParser */ 2795