2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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 License for more details.
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
22 #include "NGPop3Client.h"
23 #include "NGPop3Support.h"
24 #include "NGMimeMessageParser.h"
25 #include "NGMimeMessage.h"
28 @implementation NGPop3Client
37 s = [NGActiveSocket socketInDomain:[NGInternetSocketDomain domain]];
38 return [[[self alloc] initWithSocket:s] autorelease];
42 NSLog(@"%@: init not supported, use initWithSocket: ..", self);
47 - (id)initWithSocket:(id<NGActiveSocket>)_socket {
48 if ((self = [super init])) {
49 self->socket = [_socket retain];
50 NSAssert(self->socket, @"invalid socket parameter");
53 [(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_socket];
55 [(NGCTextStream *)[NGCTextStream alloc] initWithSource:self->connection];
57 self->state = [self->socket isConnected]
58 ? NGPop3State_AUTHORIZATION
59 : NGPop3State_unconnected;
66 [self->connection release];
67 [self->socket release];
68 [self->lastResponse release];
74 - (id<NGActiveSocket>)socket {
78 - (NGPop3State)state {
82 - (NGPop3Response *)lastResponse {
83 return self->lastResponse;
86 - (void)setDebuggingEnabled:(BOOL)_flag {
87 self->isDebuggingEnabled = _flag;
89 - (BOOL)isDebuggingEnabled {
90 return self->isDebuggingEnabled;
95 - (BOOL)connectToAddress:(id<NGSocketAddress>)_address {
96 NSString *greeting = nil;
98 [self requireState:NGPop3State_unconnected];
100 [self->socket connectToAddress:_address];
102 // receive greeting from server
103 greeting = [self->text readLineAsString];
104 if (self->isDebuggingEnabled)
105 [NGTextErr writeFormat:@"S: %@\n", greeting];
108 if (![greeting hasPrefix:@"+OK"])
111 // we are welcome, need to authorize
112 [self gotoState:NGPop3State_AUTHORIZATION];
116 - (BOOL)connectToHost:(id)_host {
117 return [self connectToAddress:[NGInternetSocketAddress addressWithService:@"pop3"
125 [self gotoState:NGPop3State_unconnected];
130 - (NGPop3Response *)receiveSimpleReply {
131 NSString *line = [self->text readLineAsString];
134 NGPop3Response *response = [NGPop3Response responseWithLine:line];
135 ASSIGN(self->lastResponse, response);
138 [self->lastResponse release];
139 self->lastResponse = nil;
141 return self->lastResponse;
144 - (BOOL)receiveMultilineReply:(NSMutableData *)_data {
151 } pState = NGPop3_begin;
152 void (*addBytes)(id self, SEL _cmd, void *buffer, unsigned int _bufLen);
155 addBytes = (void*)[_data methodForSelector:@selector(appendBytes:length:)];
158 c = [self->connection readByte];
160 NSLog(@"ERROR: connection was shut down ..");
165 if (c >= 32) printf("%i '%c'\n", c, c);
166 else printf("%i\n", c);
169 NSAssert((c >= 0) && (c <= 255), @"invalid byte read ..");
171 if (pState == NGPop3_foundCRLFP) {
172 if (c == '\r') { // CR LF . CR
173 addBytes(_data, @selector(appendBytes:length:), "\r\n", 2);
174 c = [self->connection readByte];
176 pState = NGPop3_done;
180 NSLog(@"WARNING: found strange sequence: 'CR LF . CR 0x%x'", c);
181 addBytes(_data, @selector(appendBytes:length:), ".\r", 2);
182 addBytes(_data, @selector(appendBytes:length:), &c8, 1);
183 pState = NGPop3_begin;
186 else if (c == '\n') { // CR LF . LF
187 NSLog(@"WARNING: found strange sequence: 'CR LF . LF'");
188 addBytes(_data, @selector(appendBytes:length:), "\r\n.\n", 4);
189 pState = NGPop3_begin;
191 else { // CR LF . (.|other)
194 NSLog(@"WARNING: expected '\\r\\n.\\r' or '\\r\\n..', got '\\r\\n.%c'", c);
195 addBytes(_data, @selector(appendBytes:length:), "\r\n", 2);
196 addBytes(_data, @selector(appendBytes:length:), &c8, 1);
197 pState = NGPop3_begin;
201 else if (pState == NGPop3_foundCRLF) {
202 if (c == '.') { // found: CR LF .
203 pState = NGPop3_foundCRLFP;
206 else if (c == '\r') {
207 addBytes(_data, @selector(appendBytes:length:), "\r\n", 2);
208 pState = NGPop3_foundCR;
213 addBytes(_data, @selector(appendBytes:length:), "\r\n", 2);
214 addBytes(_data, @selector(appendBytes:length:), &c8, 1);
215 pState = NGPop3_begin;
218 else if (pState == NGPop3_foundCR) {
219 if (c == '\n') { // found CR LF
220 pState = NGPop3_foundCRLF;
225 addBytes(_data, @selector(appendBytes:length:), "\r", 1);
226 addBytes(_data, @selector(appendBytes:length:), &c8, 1);
227 pState = NGPop3_begin;
230 else if (c == '\r') {
231 pState = NGPop3_foundCR;
235 else if (c == '\n') {
236 NSLog(@"WARNING: found LF without leading CR ..");
237 pState = NGPop3_foundCRLF;
242 addBytes(_data, @selector(appendBytes:length:), &c8, 1);
245 while(pState != NGPop3_done);
247 return (pState == NGPop3_done) ? YES : NO;
250 - (NGPop3Response *)sendCommand:(NSString *)_command {
251 if (self->isDebuggingEnabled) {
252 [NGTextOut writeFormat:@"C: %@\n", _command];
256 [text writeString:_command];
257 [text writeString:@"\r\n"];
259 return [self receiveSimpleReply];
262 - (NGPop3Response *)sendCommand:(NSString *)_command argument:(NSString *)_argument {
263 if (self->isDebuggingEnabled) {
264 if (![_command isEqualToString:@"PASS"])
265 [NGTextOut writeFormat:@"C: %@ %@\n", _command, _argument];
267 [NGTextOut writeFormat:@"C: PASS <hidden>\n"];
270 [text writeString:_command];
271 [text writeFormat:@" %s\r\n", [_argument cString]];
273 return [self receiveSimpleReply];
275 - (NGPop3Response *)sendCommand:(NSString *)_command intArgument:(int)_argument {
276 if (self->isDebuggingEnabled) {
277 if (![_command isEqualToString:@"PASS"])
278 [NGTextOut writeFormat:@"C: %@ %i\n", _command, _argument];
280 [NGTextOut writeFormat:@"C: PASS <hidden>\n"];
283 [text writeString:_command];
284 [text writeFormat:@" %i\r\n", _argument];
286 return [self receiveSimpleReply];
288 - (NGPop3Response *)sendCommand:(NSString *)_command
289 intArgument:(int)_arg1 intArgument:(int)_arg2 {
291 if (self->isDebuggingEnabled) {
292 if (![_command isEqualToString:@"PASS"])
293 [NGTextOut writeFormat:@"C: %@ %i %i\n", _command, _arg1, _arg2];
295 [NGTextOut writeFormat:@"C: PASS <hidden>\n"];
298 [text writeString:_command];
299 [text writeFormat:@" %i %i\r\n", _arg1, _arg2];
301 return [self receiveSimpleReply];
306 - (void)requireState:(NGPop3State)_state {
307 if (_state != [self state]) {
308 [[[NGPop3StateException alloc]
310 requiredState:_state] raise];
314 - (void)gotoState:(NGPop3State)_state {
315 self->state = _state;
320 - (BOOL)login:(NSString *)_user password:(NSString *)_passwd {
321 NGPop3Response *reply = nil;
323 [self requireState:NGPop3State_AUTHORIZATION];
325 reply = [self sendCommand:@"USER" argument:_user];
326 if ([reply isPositive]) {
327 reply = [self sendCommand:@"PASS" argument:_passwd];
328 if ([reply isPositive]) {
329 [self gotoState:NGPop3State_TRANSACTION];
333 NSLog(@"POP3 authorization of user %@ failed ..", _user);
339 NGPop3Response *reply = nil;
341 reply = [self sendCommand:@"QUIT"];
342 if ([reply isPositive]) {
343 unsigned int waitBytes = 0;
345 if (self->state == NGPop3State_TRANSACTION)
346 self->state = NGPop3State_UPDATE;
348 if (self->isDebuggingEnabled)
349 [NGTextErr writeFormat:@"S: %@\n", [reply line]];
351 // wait for connection close ..
352 while ([self->connection readByte] != -1)
355 self->state = NGPop3State_unconnected;
357 return [reply isPositive];
360 - (BOOL)statMailDropCount:(int *)_count size:(int *)_size {
361 NGPop3Response *reply = nil;
362 [self requireState:NGPop3State_TRANSACTION];
366 reply = [self sendCommand:@"STAT"];
368 if ([reply isPositive]) {
369 const char *cstr = [[reply line] cString];
371 while ((*cstr != '\0') && (*cstr != ' ')) cstr++;
372 if (*cstr == '\0') return NO;
375 *_count = atoi(cstr);
376 while ((*cstr != '\0') && (*cstr != ' ')) cstr++;
377 if (*cstr == '\0') return NO;
387 - (NGPop3MessageInfo *)listMessage:(int)_messageNumber {
388 NGPop3Response *reply = nil;
389 [self requireState:NGPop3State_TRANSACTION];
391 reply = [self sendCommand:@"LIST" intArgument:_messageNumber];
392 if ([reply isPositive]) {
393 const char *cstr = index([[reply line] cString], ' ');
399 cstr = index(cstr, ' ') + 1;
400 if (cstr > (char *)1) {
401 NGPop3MessageInfo *info = nil;
402 int msgSize = atoi(cstr);
404 info = [NGPop3MessageInfo infoForMessage:msgNum size:msgSize client:self];
408 NSLog(@"ERROR: invalid reply line '%@' ..", [reply line]);
413 - (NSEnumerator *)listMessages {
414 NGPop3Response *reply = nil;
415 [self requireState:NGPop3State_TRANSACTION];
417 reply = [self sendCommand:@"LIST"];
418 if ([reply isPositive]) {
419 NSMutableArray *array = nil;
420 NSString *line = nil;
422 array = [NSMutableArray arrayWithCapacity:128];
424 line = [self->text readLineAsString];
425 while ((line != nil) && (![line isEqualToString:@"."])) {
426 NGPop3MessageInfo *info = nil;
427 const char *cstr = (char *)[line cString];
431 cstr = index(cstr, ' ') + 1;
432 if (cstr > (char *)1)
433 msgSize = atoi(cstr);
435 NSLog(@"WARNING(%s): invalid reply line '%@'", __PRETTY_FUNCTION__, line);
439 info = [NGPop3MessageInfo infoForMessage:msgNum size:msgSize client:self];
442 [array addObject:info];
444 NSLog(@"ERROR: could not produce info for line '%@'", line);
445 line = [self->text readLineAsString];
448 return [array objectEnumerator];
454 - (NSData *)retrieveMessage:(int)_msgNumber {
455 NGPop3Response *reply = nil;
456 [self requireState:NGPop3State_TRANSACTION];
458 reply = [self sendCommand:@"RETR" intArgument:_msgNumber];
459 if ([reply isPositive]) {
460 NSMutableData *data = nil;
461 const char *cstr = index([[reply line] cString], ' ');
462 unsigned msgSize = -1;
466 msgSize = atoi(cstr);
467 data = [NSMutableData dataWithCapacity:msgSize + 1];
470 data = [NSMutableData dataWithCapacity:1024];
472 if ([self receiveMultilineReply:data]) {
473 if ((msgSize > 0) && ([data length] > msgSize)) {
474 NSLog(@"data was longer than message size ..");
475 //[data setLength:msgSize];
483 - (BOOL)deleteMessage:(int)_msgNumber {
484 NGPop3Response *reply = nil;
485 [self requireState:NGPop3State_TRANSACTION];
487 reply = [self sendCommand:@"DELE" intArgument:_msgNumber];
488 if ([reply isPositive]) {
495 [self requireState:NGPop3State_TRANSACTION];
496 return [[self sendCommand:@"NOOP"] isPositive];
500 [self requireState:NGPop3State_TRANSACTION];
501 return [[self sendCommand:@"RSET"] isPositive];
504 // optional service commands
506 - (NSData *)retrieveMessage:(int)_msgNumber bodyLineCount:(int)_numberOfLines {
507 NGPop3Response *reply = nil;
508 [self requireState:NGPop3State_TRANSACTION];
510 reply = [self sendCommand:@"TOP"
511 intArgument:_msgNumber
512 intArgument:_numberOfLines];
513 if ([reply isPositive]) {
514 NSMutableData *data = nil;
515 const char *cstr = index([[reply line] cString], ' ');
520 msgSize = atoi(cstr);
522 data = [NSMutableData dataWithCapacity:1024];
524 if ([self receiveMultilineReply:data])
530 - (NSDictionary *)uniqueIdMappings {
531 NGPop3Response *reply = nil;
532 [self requireState:NGPop3State_TRANSACTION];
534 reply = [self sendCommand:@"UIDL"];
535 if ([reply isPositive]) {
536 NSMutableDictionary *dict = nil;
537 NSString *line = nil;
539 dict = [NSMutableDictionary dictionaryWithCapacity:256];
541 line = [self->text readLineAsString];
542 while ((line != nil) && (![line isEqualToString:@"."])) {
543 const char *cstr = index([line cString], ' ');
546 int msgNum = atoi([line cString]);
549 [dict setObject:[NSString stringWithCString:cstr]
550 forKey:[NSNumber numberWithInt:msgNum]];
553 NSLog(@"WARNING(%s): invalid reply line '%@'", __PRETTY_FUNCTION__, line);
555 line = [self->text readLineAsString];
564 - (NSString *)uniqueIdOfMessage:(int)_messageNumber {
565 NGPop3Response *reply = nil;
566 [self requireState:NGPop3State_TRANSACTION];
568 reply = [self sendCommand:@"UIDL" intArgument:_messageNumber];
569 if ([reply isPositive]) {
570 const char *cstr = index([[reply line] cString], ' ');
572 if (cstr) { // found message number
573 cstr = index(cstr + 1, ' ');
574 if (cstr) { // found u-id
576 return [NSString stringWithCString:cstr];
579 NSLog(@"ERROR: invalid reply line '%@' ..", [reply line]);
586 - (NSEnumerator *)messageEnumerator {
587 return [[[NGPop3MailDropEnumerator alloc]
588 initWithMessageInfoEnumerator:[self listMessages]] autorelease];
590 - (NGMimeMessage *)messageWithNumber:(int)_messageNumber {
591 NSData *msgData = [self retrieveMessage:_messageNumber];
594 NGDataStream *msgStream;
595 NGMimeMessageParser *parser;
596 NGMimeMessage *message;
598 msgStream = [[NGDataStream alloc] initWithData:msgData];
599 parser = [[NGMimeMessageParser alloc] init];
603 message = (NGMimeMessage *)[parser parsePartFromStream:msgStream];
608 message = [message retain];
610 [parser release]; parser = nil;
611 [msgStream release]; msgStream = nil;
614 return [message autorelease];
622 - (NSString *)description {
623 return [NSString stringWithFormat:@"<POP3Client[0x%08X]: socket=%@>",
624 (unsigned)self, [self socket]];