1/** GSFTPURLHandle.m - Class GSFTPURLHandle 2 Copyright (C) 2002 Free Software Foundation, Inc. 3 4 Written by: Richard Frith-Macdonald <rfm@gnu.org> 5 Date: June 2002 6 7 This file is part of the GNUstep Library. 8 9 This library is free software; you can redistribute it and/or 10 modify it under the terms of the GNU Lesser General Public 11 License as published by the Free Software Foundation; either 12 version 2 of the License, or (at your option) any later version. 13 14 This library is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 Lesser General Public License for more details. 18 19 You should have received a copy of the GNU Lesser General Public 20 License along with this library; if not, write to the Free 21 Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 Boston, MA 02110 USA. 23*/ 24 25#import "common.h" 26#import "Foundation/NSArray.h" 27#import "Foundation/NSDictionary.h" 28#import "Foundation/NSEnumerator.h" 29#import "Foundation/NSException.h" 30#import "Foundation/NSValue.h" 31#import "Foundation/NSData.h" 32#import "Foundation/NSDictionary.h" 33#import "Foundation/NSEnumerator.h" 34#import "Foundation/NSURL.h" 35#import "Foundation/NSURLHandle.h" 36#import "Foundation/NSNotification.h" 37#import "Foundation/NSRunLoop.h" 38#import "Foundation/NSByteOrder.h" 39#import "Foundation/NSLock.h" 40#import "Foundation/NSFileHandle.h" 41#import "GNUstepBase/GSMime.h" 42#import "GSPrivate.h" 43 44GS_EXPORT NSString * const GSTelnetNotification; 45GS_EXPORT NSString * const GSTelnetErrorKey; 46GS_EXPORT NSString * const GSTelnetTextKey; 47 48@interface GSTelnetHandle : NSObject 49{ 50 NSStringEncoding enc; 51 NSFileHandle *remote; 52 NSMutableData *ibuf; 53 unsigned pos; 54 BOOL lineMode; 55 BOOL connected; 56} 57- (id) initWithHandle: (NSFileHandle*)handle isConnected: (BOOL)flag; 58- (void) putTelnetLine: (NSString*)s; 59- (void) putTelnetText: (NSString*)s; 60- (void) setEncoding: (NSStringEncoding)e; 61- (void) setLineMode: (BOOL)flag; 62@end 63 64 65 66@interface GSTelnetHandle (Private) 67- (void) _didConnect: (NSNotification*)notification; 68- (void) _didRead: (NSNotification*)notification; 69- (void) _didWrite: (NSNotification*)notification; 70@end 71 72NSString * const GSTelnetNotification = @"GSTelnetNotification"; 73NSString * const GSTelnetErrorKey = @"GSTelnetErrorKey"; 74NSString * const GSTelnetTextKey = @"GSTelnetTextKey"; 75 76@implementation GSTelnetHandle 77 78#define WILL 251 79#define WONT 252 80#define DO 253 81#define DONT 254 82#define IAC 255 83 84- (void) dealloc 85{ 86 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 87 88 [nc removeObserver: self]; 89 RELEASE(remote); 90 RELEASE(ibuf); 91 [super dealloc]; 92} 93 94- (id) init 95{ 96 return [self initWithHandle: nil isConnected: NO]; 97} 98 99- (id) initWithHandle: (NSFileHandle*)handle isConnected: (BOOL)flag 100{ 101 if (handle == nil) 102 { 103 DESTROY(self); 104 } 105 else 106 { 107 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 108 109 connected = flag; 110 enc = NSUTF8StringEncoding; 111 ibuf = [NSMutableData new]; 112 remote = RETAIN(handle); 113 if (connected == YES) 114 { 115 [nc addObserver: self 116 selector: @selector(_didRead:) 117 name: NSFileHandleReadCompletionNotification 118 object: remote]; 119 [nc addObserver: self 120 selector: @selector(_didWrite:) 121 name: GSFileHandleWriteCompletionNotification 122 object: remote]; 123 [remote readInBackgroundAndNotify]; 124 } 125 else 126 { 127 [nc addObserver: self 128 selector: @selector(_didConnect:) 129 name: GSFileHandleConnectCompletionNotification 130 object: remote]; 131 } 132 } 133 return self; 134} 135 136- (void) putTelnetLine: (NSString*)s 137{ 138 if ([s hasSuffix: @"\n"] == NO) 139 { 140 s = [s stringByAppendingString: @"\r\n"]; 141 } 142 [self putTelnetText: s]; 143} 144 145- (void) putTelnetText: (NSString*)s 146{ 147 NSMutableData *md; 148 unsigned char *to; 149 NSData *d = [s dataUsingEncoding: enc]; 150 unsigned char *from = (unsigned char *)[d bytes]; 151 unsigned int len = [d length]; 152 unsigned int i = 0; 153 unsigned int count = 0; 154 155 for (i = 0; i < len; i++) 156 { 157 if (from[i] == IAC) 158 { 159 count++; 160 } 161 } 162 163 md = [[NSMutableData alloc] initWithLength: len + count]; 164 to = [md mutableBytes]; 165 for (i = 0; i < len; i++) 166 { 167 if (*from == IAC) 168 { 169 *to++ = IAC; 170 } 171 *to++ = *from++; 172 } 173//NSLog(@"Write - '%*.*s'", [md length], [md length], [md bytes]); 174 [remote writeInBackgroundAndNotify: md]; 175 DESTROY(md); 176} 177 178/** 179 * Set the string encoding used to convert strings to be sent to the 180 * remote system into raw data, and to convert incoming data from that 181 * system inot input text. 182 */ 183- (void) setEncoding: (NSStringEncoding)e 184{ 185 enc = e; 186} 187 188/** 189 * Sets a flag to say whether observers are to be notified of incoming 190 * data after every chunk read, or only when one or more entire lines 191 * are read. <br /> 192 * When switching out of line mode, this will cause a notification to be 193 * generated if there is any buffered data available for use. 194 */ 195- (void) setLineMode: (BOOL)flag 196{ 197 if (lineMode != flag) 198 { 199 lineMode = flag; 200 if (lineMode == NO) 201 { 202 [self _didRead: nil]; 203 } 204 } 205} 206 207@end 208 209@implementation GSTelnetHandle (Private) 210- (void) _didConnect: (NSNotification*)notification 211{ 212 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 213 NSDictionary *info = [notification userInfo]; 214 NSString *e; 215 216 e = [info objectForKey: GSFileHandleNotificationError]; 217 if (e == nil) 218 { 219 [nc removeObserver: self 220 name: GSFileHandleConnectCompletionNotification 221 object: [notification object]]; 222 [nc addObserver: self 223 selector: @selector(_didRead:) 224 name: NSFileHandleReadCompletionNotification 225 object: remote]; 226 [nc addObserver: self 227 selector: @selector(_didWrite:) 228 name: GSFileHandleWriteCompletionNotification 229 object: remote]; 230 [remote readInBackgroundAndNotify]; 231 } 232 else 233 { 234 info = [NSDictionary dictionaryWithObject: e 235 forKey: GSTelnetErrorKey]; 236 [nc postNotificationName: GSTelnetNotification 237 object: self 238 userInfo: info]; 239 } 240} 241 242- (void) _didRead: (NSNotification*)notification 243{ 244 NSDictionary *userInfo = [notification userInfo]; 245 NSMutableArray *text = nil; 246 NSData *d; 247 248 d = [userInfo objectForKey: NSFileHandleNotificationDataItem]; 249 /* 250 * If the notification is nil, this method has been called to flush 251 * any buffered data out. 252 */ 253 if (notification != nil && (d == nil || [d length] == 0)) 254 { 255 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 256 NSDictionary *info; 257 258 info = [NSDictionary dictionaryWithObject: @"EOF" 259 forKey: GSTelnetErrorKey]; 260 [nc postNotificationName: GSTelnetNotification 261 object: self 262 userInfo: info]; 263 } 264 else 265 { 266 NSMutableData *toWrite = nil; 267 unsigned char *ptr; 268 unsigned char c; 269 unsigned int s = 0; 270 int old; 271 int len; 272 int i; // May be negative. 273 274 if (d != nil) 275 { 276// NSLog(@"Read - '%@'", d); 277 [ibuf appendData: d]; 278 } 279 old = len = (int)[ibuf length]; 280 ptr = [ibuf mutableBytes]; 281 282 for (i = pos; i < len; i++) 283 { 284 NSData *line = nil; 285 286 c = ptr[i]; 287 if (c == IAC) 288 { 289 if (i < len - 1) 290 { 291 c = ptr[i+1]; 292 if (c == WILL || c == WONT || c == DO || c == DONT) 293 { 294 /* 295 * refuse any negotiation attempts. 296 */ 297 if (c == WILL || c == DO) 298 { 299 unsigned char opt[3]; 300 301 if (toWrite == nil) 302 { 303 toWrite = [NSMutableData alloc]; 304 toWrite = [toWrite initWithCapacity: 12]; 305 } 306 opt[0] = IAC; 307 if (c == DO) 308 { 309 opt[1] = WONT; 310 } 311 else 312 { 313 opt[1] = DONT; 314 } 315 opt[2] = ptr[i+2]; 316 [toWrite appendBytes: opt length: 3]; 317 } 318 if (i < len - 2) 319 { 320// NSLog(@"Command: %d %d", ptr[1], ptr[2]); 321 len -= 3; 322 if (len - i > 0) 323 { 324 memmove(ptr, &ptr[3], len - i); 325 } 326 i--; // Try again. 327 } 328 else 329 { 330 i--; 331 break; // Need more data 332 } 333 } 334 else if (c == IAC) // Escaped IAC 335 { 336 len--; 337 if (len - i > 0) 338 { 339 memmove(ptr, &ptr[1], len - i); 340 } 341 } 342 else 343 { 344// NSLog(@"Command: %d", ptr[1]); 345 /* 346 * Strip unimplemented escape sequence. 347 */ 348 len -= 2; 349 if (len - i > 0) 350 { 351 memmove(ptr, &ptr[2], len - i); 352 } 353 i--; // Try again from here. 354 } 355 } 356 else 357 { 358 i--; 359 break; // Need more data 360 } 361 } 362 else if (c == '\r' && (int)i < len - 1 && ptr[i+1] == '\n') 363 { 364 line = [[NSData alloc] initWithBytes: &ptr[s] length: i-s+2]; 365 i++; 366 s = i + 1; 367 } 368 else if (c == '\n') 369 { 370 line = [[NSData alloc] initWithBytes: &ptr[s] length: i-s+1]; 371 s = i + 1; 372 } 373 if (line != nil) 374 { 375 NSString *lineString; 376 377 lineString = [[NSString alloc] initWithData: line encoding: enc]; 378 DESTROY(line); 379 if (text == nil) 380 { 381 text = [[NSMutableArray alloc] initWithCapacity: 4]; 382 } 383 [text addObject: lineString]; 384 DESTROY(lineString); 385 } 386 } 387 pos = i; 388 389 /* 390 * If not in line mode, we can add the remainder of the data to 391 * the array of strings for notification. 392 */ 393 if (lineMode == NO && s != pos) 394 { 395 NSString *lineString; 396 NSData *line; 397 398 line = [[NSData alloc] initWithBytes: &ptr[s] length: pos - s]; 399 s = pos; 400 lineString = [[NSString alloc] initWithData: line encoding: enc]; 401 DESTROY(line); 402 if (text == nil) 403 { 404 text = [[NSMutableArray alloc] initWithCapacity: 4]; 405 } 406 [text addObject: lineString]; 407 DESTROY(lineString); 408 } 409 410 /* 411 * Adjust size of data remaining in buffer if necessary. 412 */ 413 if (old != len || s > 0) 414 { 415 if (s > 0) 416 { 417 len -= s; 418 pos -= s; 419 if (len > 0) 420 { 421 memmove(ptr, &ptr[s], len); 422 } 423 } 424 [ibuf setLength: len]; 425 } 426 427 /* 428 * Send telnet protocol negotion info if necessary. 429 */ 430 if (toWrite != nil) 431 { 432// NSLog(@"Write - '%@'", toWrite); 433 [remote writeInBackgroundAndNotify: toWrite]; 434 DESTROY(toWrite); 435 } 436 437 /* 438 * Send notification for text read as necessary. 439 */ 440 if (text != nil) 441 { 442 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 443 NSNotification *n; 444 NSDictionary *info; 445 446 info = [NSDictionary dictionaryWithObject: text 447 forKey: GSTelnetTextKey]; 448 DESTROY(text); 449 n = [NSNotification notificationWithName: GSTelnetNotification 450 object: self 451 userInfo: info]; 452 [nc postNotification: n]; 453 } 454 [remote readInBackgroundAndNotify]; 455 } 456} 457 458- (void) _didWrite: (NSNotification*)notification 459{ 460 NSDictionary *userInfo = [notification userInfo]; 461 NSString *e; 462 463 e = [userInfo objectForKey: GSFileHandleNotificationError]; 464 if (e) 465 { 466 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 467 NSDictionary *info; 468 469 info = [NSDictionary dictionaryWithObject: e 470 forKey: GSTelnetErrorKey]; 471 [nc postNotificationName: GSTelnetNotification 472 object: self 473 userInfo: info]; 474 } 475} 476@end 477 478 479 480@interface GSFTPURLHandle : NSURLHandle 481{ 482 GSTelnetHandle *cHandle; 483 NSFileHandle *dHandle; 484 NSURL *url; 485 NSData *wData; 486 NSString *term; 487 enum { 488 idle, 489 cConnect, // Establishing control connection 490 sentUser, // Sent username 491 sentPass, // Sent password 492 sentType, // Sent data type 493 sentPasv, // Requesting host/port information for data link 494 data, // Establishing or using data connection 495 list, // listing directory 496 } state; 497} 498- (void) _control: (NSNotification*)n; 499- (void) _data: (NSNotification*)n; 500@end 501 502/** 503 * <p> 504 * This is a <em>PRIVATE</em> subclass of NSURLHandle. 505 * It is documented here in order to give you information about the 506 * default behavior of an NSURLHandle created to deal with a URL 507 * that has the <code>ftp</code> scheme. 508 * The name and/or other implementation details of this class 509 * may be changed at any time. 510 * </p> 511 * <p> 512 * A GSFTPURLHandle instance is used to manage connections to 513 * <code>ftp</code> URLs. 514 * </p> 515 */ 516@implementation GSFTPURLHandle 517 518static NSMutableDictionary *urlCache = nil; 519static NSLock *urlLock = nil; 520 521+ (NSURLHandle*) cachedHandleForURL: (NSURL*)newUrl 522{ 523 NSURLHandle *obj = nil; 524 525 if ([[newUrl scheme] caseInsensitiveCompare: @"ftp"] == NSOrderedSame) 526 { 527 NSString *page = [newUrl absoluteString]; 528// NSLog(@"Lookup for handle for '%@'", page); 529 [urlLock lock]; 530 obj = [urlCache objectForKey: page]; 531 IF_NO_GC([[obj retain] autorelease];) 532 [urlLock unlock]; 533// NSLog(@"Found handle %@", obj); 534 } 535 return obj; 536} 537 538+ (void) initialize 539{ 540 if (self == [GSFTPURLHandle class]) 541 { 542 urlCache = [NSMutableDictionary new]; 543 [[NSObject leakAt: &urlCache] release]; 544 urlLock = [NSLock new]; 545 [[NSObject leakAt: &urlLock] release]; 546 } 547} 548 549- (void) dealloc 550{ 551 if (state != idle) 552 { 553 [self endLoadInBackground]; 554 } 555 RELEASE(url); 556 RELEASE(wData); 557 RELEASE(term); 558 [super dealloc]; 559} 560 561- (id) initWithURL: (NSURL*)newUrl 562 cached: (BOOL)cached 563{ 564 if ((self = [super initWithURL: newUrl cached: cached]) != nil) 565 { 566 ASSIGN(url, newUrl); 567 state = idle; 568 if (cached == YES) 569 { 570 NSString *page = [newUrl absoluteString]; 571 572 [urlLock lock]; 573 [urlCache setObject: self forKey: page]; 574 [urlLock unlock]; 575// NSLog(@"Cache handle %@ for '%@'", self, page); 576 } 577 } 578 return self; 579} 580 581+ (BOOL) canInitWithURL: (NSURL*)newUrl 582{ 583 if ([[newUrl scheme] isEqualToString: @"ftp"] == YES) 584 { 585 return YES; 586 } 587 return NO; 588} 589 590- (void) _control: (NSNotification*)n 591{ 592 NSDictionary *info = [n userInfo]; 593 NSString *e = [info objectForKey: GSTelnetErrorKey]; 594 NSArray *text; 595 NSString *line; 596 597 if (e == nil) 598 { 599 NSEnumerator *enumerator; 600 601 text = [info objectForKey: GSTelnetTextKey]; 602// NSLog(@"Ctl: %@", text); 603 /* Find first reply line which is not a continuation of another. 604 */ 605 enumerator = [text objectEnumerator]; 606 while ((line = [enumerator nextObject]) != nil) 607 { 608 if (term == nil) 609 { 610 if ([line length] > 4) 611 { 612 char buf[4]; 613 614 buf[0] = (char)[line characterAtIndex: 0]; 615 buf[1] = (char)[line characterAtIndex: 1]; 616 buf[2] = (char)[line characterAtIndex: 2]; 617 buf[3] = (char)[line characterAtIndex: 3]; 618 if (isdigit(buf[0]) && isdigit(buf[1]) && isdigit(buf[2])) 619 { 620 if (buf[3] == '-') 621 { 622 /* Got start of a multiline block ... 623 * set the terminator we need to look for. 624 */ 625 buf[3] = ' '; 626 term = [[NSString alloc] 627 initWithCString: buf length: 4]; 628 } 629 else if (buf[3] == ' ') 630 { 631 /* Found single line response. 632 */ 633 break; 634 } 635 } 636 } 637 } 638 else if ([line hasPrefix: term] == YES) 639 { 640 /* Found end of a multiline response. 641 */ 642 DESTROY(term); 643 break; 644 } 645 } 646 if (line == nil) 647 { 648 return; 649 } 650 651 if (state == cConnect) 652 { 653 if ([line hasPrefix: @"2"] == YES) 654 { 655 NSString *user = [url user]; 656 657 if (user == nil) 658 { 659 user = @"anonymous"; 660 } 661 [cHandle putTelnetLine: [@"USER " stringByAppendingString: user]]; 662 state = sentUser; 663 } 664 else 665 { 666 e = line; 667 } 668 } 669 else if (state == sentUser) 670 { 671 if ([line hasPrefix: @"230"] == YES) // No password required 672 { 673 [cHandle putTelnetLine: @"TYPE I"]; 674 state = sentType; 675 } 676 else if ([line hasPrefix: @"331"] == YES) 677 { 678 NSString *pass = [url password]; 679 680 if (pass == nil) 681 { 682 pass = [url user]; 683 if (pass == nil) 684 { 685 pass = @"GNUstep@here"; 686 } 687 else 688 { 689 pass = @""; 690 } 691 } 692 [cHandle putTelnetLine: [@"PASS " stringByAppendingString: pass]]; 693 state = sentPass; 694 } 695 else 696 { 697 e = line; 698 } 699 } 700 else if (state == sentPass) 701 { 702 if ([line hasPrefix: @"2"] == YES) 703 { 704 [cHandle putTelnetLine: @"TYPE I"]; 705 state = sentType; 706 } 707 else 708 { 709 e = line; 710 } 711 } 712 else if (state == sentType) 713 { 714 if ([line hasPrefix: @"2"] == YES) 715 { 716 [cHandle putTelnetLine: @"PASV"]; 717 state = sentPasv; 718 } 719 else 720 { 721 e = line; 722 } 723 } 724 else if (state == sentPasv) 725 { 726 if ([line hasPrefix: @"227"] == YES) 727 { 728 NSRange r = [line rangeOfString: @"("]; 729 NSString *h = nil; 730 NSString *p = nil; 731 732 if (r.length > 0) 733 { 734 unsigned pos = NSMaxRange(r); 735 736 r = [line rangeOfString: @")"]; 737 if (r.length > 0 && r.location > pos) 738 { 739 NSArray *a; 740 741 r = NSMakeRange(pos, r.location - pos); 742 line = [line substringWithRange: r]; 743 a = [line componentsSeparatedByString: @","]; 744 if ([a count] == 6) 745 { 746 h = [NSString stringWithFormat: @"%@.%@.%@.%@", 747 [a objectAtIndex: 0], [a objectAtIndex: 1], 748 [a objectAtIndex: 2], [a objectAtIndex: 3]]; 749 p = [NSString stringWithFormat: @"%d", 750 [[a objectAtIndex: 4] intValue] * 256 751 + [[a objectAtIndex: 5] intValue]]; 752 } 753 } 754 } 755 if (h == nil) 756 { 757 e = @"Invalid host/port information for pasv command"; 758 } 759 else 760 { 761 NSNotificationCenter *nc; 762 763 dHandle = [NSFileHandle 764 fileHandleAsClientInBackgroundAtAddress: h service: p 765 protocol: @"tcp"]; 766 IF_NO_GC([dHandle retain];) 767 nc = [NSNotificationCenter defaultCenter]; 768 [nc addObserver: self 769 selector: @selector(_data:) 770 name: GSFileHandleConnectCompletionNotification 771 object: dHandle]; 772 state = data; 773 } 774 } 775 else 776 { 777 e = line; 778 } 779 } 780 else if (state == data) 781 { 782 if ([line hasPrefix: @"550"] == YES && wData == nil) 783 { 784 state = list; 785 [cHandle putTelnetLine: 786 [NSString stringWithFormat: @"NLST %@", [url path]]]; 787 } 788 else if ([line hasPrefix: @"1"] == NO && [line hasPrefix: @"2"] == NO) 789 { 790 e = line; 791 } 792 } 793 else if (state == list) 794 { 795 if ([line hasPrefix: @"550"] == YES) 796 { 797 NSRange r = [line rangeOfString: @"not a plain file"]; 798 799 /* 800 * Some servers may return an error on listing even though 801 * the path was a valid directory. We try to catch some of 802 * those cases and produce an empty listing instead. 803 */ 804 if (r.location > 0) 805 { 806 NSNotificationCenter *nc; 807 808 nc = [NSNotificationCenter defaultCenter]; 809 if (dHandle != nil) 810 { 811 [nc removeObserver: self name: nil object: dHandle]; 812 [dHandle closeFile]; 813 DESTROY(dHandle); 814 } 815 [nc removeObserver: self 816 name: GSTelnetNotification 817 object: cHandle]; 818 DESTROY(cHandle); 819 state = idle; 820 [self didLoadBytes: [NSData data] loadComplete: YES]; 821 } 822 else 823 { 824 e = line; 825 } 826 } 827 else if ([line hasPrefix: @"1"] == NO && [line hasPrefix: @"2"] == NO) 828 { 829 e = line; 830 } 831 } 832 else 833 { 834 e = @"Message in unknown state"; 835 } 836 } 837 if (e != nil) 838 { 839 /* 840 * Tell superclass that the load failed - let it do housekeeping. 841 */ 842 [self endLoadInBackground]; 843 [self backgroundLoadDidFailWithReason: e]; 844 } 845} 846 847- (void) _data: (NSNotification*)n 848{ 849 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 850 NSString *name = [n name]; 851 NSDictionary *info = [n userInfo]; 852 NSString *e = [info objectForKey: GSFileHandleNotificationError]; 853 854// NSLog(@"_data: %@", n); 855 [nc removeObserver: self name: name object: dHandle]; 856 857 /* 858 * See if the connection attempt caused an error. 859 */ 860 if (e != nil) 861 { 862 if ([name isEqualToString: GSFileHandleConnectCompletionNotification]) 863 { 864 NSLog(@"Unable to connect to %@:%@ via socket ... %@", 865 [dHandle socketAddress], [dHandle socketService], e); 866 } 867// NSLog(@"Fail - %@", e); 868 /* 869 * Tell superclass that the load failed - let it do housekeeping. 870 */ 871 [self endLoadInBackground]; 872 [self backgroundLoadDidFailWithReason: e]; 873 return; 874 } 875 if ([name isEqualToString: GSFileHandleConnectCompletionNotification]) 876 { 877 if (wData == nil) 878 { 879 [cHandle putTelnetLine: 880 [NSString stringWithFormat: @"RETR %@", [url path]]]; 881 [nc addObserver: self 882 selector: @selector(_data:) 883 name: NSFileHandleReadCompletionNotification 884 object: dHandle]; 885 [dHandle readInBackgroundAndNotify]; 886 } 887 else 888 { 889 [cHandle putTelnetLine: 890 [NSString stringWithFormat: @"STOR %@", [url path]]]; 891 [nc addObserver: self 892 selector: @selector(_data:) 893 name: GSFileHandleWriteCompletionNotification 894 object: dHandle]; 895 [dHandle writeInBackgroundAndNotify: wData]; 896 } 897 } 898 else 899 { 900 if (wData == nil) 901 { 902 NSData *d; 903 904 d = [info objectForKey: NSFileHandleNotificationDataItem]; 905 if ([d length] > 0) 906 { 907 [self didLoadBytes: d loadComplete: NO]; 908 [nc addObserver: self 909 selector: @selector(_data:) 910 name: NSFileHandleReadCompletionNotification 911 object: dHandle]; 912 [dHandle readInBackgroundAndNotify]; 913 } 914 else 915 { 916 NSNotificationCenter *nc; 917 918 nc = [NSNotificationCenter defaultCenter]; 919 if (dHandle != nil) 920 { 921 [nc removeObserver: self name: nil object: dHandle]; 922 [dHandle closeFile]; 923 DESTROY(dHandle); 924 } 925 [nc removeObserver: self 926 name: GSTelnetNotification 927 object: cHandle]; 928 DESTROY(cHandle); 929 state = idle; 930 931 [self didLoadBytes: d loadComplete: YES]; 932 } 933 } 934 else 935 { 936 NSNotificationCenter *nc; 937 NSData *tmp; 938 939 nc = [NSNotificationCenter defaultCenter]; 940 if (dHandle != nil) 941 { 942 [nc removeObserver: self name: nil object: dHandle]; 943 [dHandle closeFile]; 944 DESTROY(dHandle); 945 } 946 [nc removeObserver: self 947 name: GSTelnetNotification 948 object: cHandle]; 949 DESTROY(cHandle); 950 state = idle; 951 952 /* 953 * Tell superclass that we have successfully loaded the data. 954 */ 955 tmp = wData; 956 wData = nil; 957 [self didLoadBytes: tmp loadComplete: YES]; 958 DESTROY(tmp); 959 } 960 } 961} 962 963- (void) endLoadInBackground 964{ 965 if (state != idle) 966 { 967 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 968 969 if (dHandle != nil) 970 { 971 [nc removeObserver: self name: nil object: dHandle]; 972 [dHandle closeFile]; 973 DESTROY(dHandle); 974 } 975 [nc removeObserver: self name: GSTelnetNotification object: cHandle]; 976 DESTROY(cHandle); 977 state = idle; 978 } 979 [super endLoadInBackground]; 980} 981 982- (void) loadInBackground 983{ 984 NSNotificationCenter *nc; 985 NSString *host = nil; 986 NSString *port = nil; 987 NSNumber *p; 988 NSFileHandle *sock; 989 990 /* 991 * Don't start a load if one is in progress. 992 */ 993 if (state != idle) 994 { 995 NSLog(@"Attempt to load an ftp handle which is not idle ... ignored"); 996 return; 997 } 998 999 [self beginLoadInBackground]; 1000 host = [url host]; 1001 p = [url port]; 1002 if (p != nil) 1003 { 1004 port = [NSString stringWithFormat: @"%u", [p unsignedIntValue]]; 1005 } 1006 else 1007 { 1008 port = [url scheme]; 1009 } 1010 sock = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: host 1011 service: port 1012 protocol: @"tcp"]; 1013 if (sock == nil) 1014 { 1015 /* 1016 * Tell superclass that the load failed - let it do housekeeping. 1017 */ 1018 [self backgroundLoadDidFailWithReason: [NSString stringWithFormat: 1019 @"Unable to connect to %@:%@ ... %@", 1020 host, port, [NSError _last]]]; 1021 return; 1022 } 1023 cHandle = [[GSTelnetHandle alloc] initWithHandle: sock isConnected: NO]; 1024 nc = [NSNotificationCenter defaultCenter]; 1025 [nc addObserver: self 1026 selector: @selector(_control:) 1027 name: GSTelnetNotification 1028 object: cHandle]; 1029 state = cConnect; 1030} 1031 1032/** 1033 * We cannot get/set any properties for FTP 1034 */ 1035- (id) propertyForKey: (NSString*)propertyKey 1036{ 1037 return nil; 1038} 1039 1040/** 1041 * We cannot get/set any properties for FTP 1042 */ 1043- (id) propertyForKeyIfAvailable: (NSString*)propertyKey 1044{ 1045 return nil; 1046} 1047 1048/** 1049 * Sets the specified data to be written to the URL on the next load. 1050 */ 1051- (BOOL) writeData: (NSData*)data 1052{ 1053 ASSIGN(wData, data); 1054 return YES; 1055} 1056 1057/** 1058 * We cannot get/set any properties for FTP 1059 */ 1060- (BOOL) writeProperty: (id)propertyValue 1061 forKey: (NSString*)propertyKey 1062{ 1063 return NO; 1064} 1065 1066@end 1067 1068