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