ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / ftp-method.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */
2
3 /* ftp-method.c - VFS modules for FTP
4
5    Copyright (C) 2000 Ian McKellar, Eazel Inc.
6
7    The Gnome Library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11
12    The Gnome Library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16
17    You should have received a copy of the GNU Library General Public
18    License along with the Gnome Library; see the file COPYING.LIB.  If not,
19    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20    Boston, MA 02111-1307, USA.
21
22    Author: Ian McKellar <yakk@yakk.net> */
23
24 /* see RFC 959 for protocol details */
25
26 #include <config.h>
27
28 /* Keep <sys/types.h> above any network includes for FreeBSD. */
29 #include <sys/types.h>
30
31 /* Keep <netinet/in.h> above <arpa/inet.h> for FreeBSD. */
32 #include <netinet/in.h>
33
34 #include <arpa/inet.h>
35 #include <libgnomevfs/gnome-vfs-context.h>
36 #include <libgnomevfs/gnome-vfs-inet-connection.h>
37 #include <libgnomevfs/gnome-vfs-socket-buffer.h>
38 #include <libgnomevfs/gnome-vfs-method.h>
39 #include <libgnomevfs/gnome-vfs-mime.h>
40 #include <libgnomevfs/gnome-vfs-mime-utils.h>
41 #include <libgnomevfs/gnome-vfs-module.h>
42 #include <libgnomevfs/gnome-vfs-module-shared.h>
43 #include <libgnomevfs/gnome-vfs-parse-ls.h>
44 #include <libgnomevfs/gnome-vfs-utils.h>
45 #include <stdio.h> /* for sscanf */
46 #include <stdlib.h> /* for atoi */
47 #include <string.h>
48 #ifdef HAVE_STRINGS_H
49 #include <strings.h>
50 #endif
51 #include <time.h>
52
53 /* maximum size of response we're expecting to get */
54 #define MAX_RESPONSE_SIZE 4096 
55
56 /* macros for the checking of FTP response codes */
57 #define IS_100(X) ((X) >= 100 && (X) < 200)
58 #define IS_200(X) ((X) >= 200 && (X) < 300)
59 #define IS_300(X) ((X) >= 300 && (X) < 400)
60 #define IS_400(X) ((X) >= 400 && (X) < 500)
61 #define IS_500(X) ((X) >= 500 && (X) < 600)
62
63 typedef struct {
64         GnomeVFSMethodHandle method_handle;
65         GnomeVFSInetConnection *inet_connection;
66         GnomeVFSSocketBuffer *socket_buf;
67         GnomeVFSURI *uri;
68         gchar *cwd;
69         GString *response_buffer;
70         gchar *response_message;
71         gint response_code;
72         GnomeVFSInetConnection *data_connection;
73         GnomeVFSSocketBuffer *data_socketbuf;
74         enum {
75                 FTP_NOTHING,
76                 FTP_READ,
77                 FTP_WRITE,
78                 FTP_READDIR
79         } operation;
80         gchar *dirlist;
81         gchar *dirlistptr;
82         gchar *server_type; /* the response from TYPE */
83         gboolean anonymous;
84         GnomeVFSResult fivefifty; /* the result to return for an FTP 550 */
85         GnomeVFSFileInfoOptions file_info_options;
86 } FtpConnection;
87
88 static const char USE_PROXY_KEY[] = "/system/http_proxy/use_http_proxy";
89
90
91 static GnomeVFSResult do_open            (GnomeVFSMethod               *method,
92                                           GnomeVFSMethodHandle         **method_handle,
93                                           GnomeVFSURI                   *uri,
94                                           GnomeVFSOpenMode               mode,
95                                           GnomeVFSContext               *context);
96 static gboolean       do_is_local        (GnomeVFSMethod               *method,
97                                           const GnomeVFSURI             *uri);
98 static GnomeVFSResult do_open_directory  (GnomeVFSMethod                *method,
99                                           GnomeVFSMethodHandle         **method_handle,
100                                           GnomeVFSURI                   *uri,
101                                           GnomeVFSFileInfoOptions        options,
102                                           GnomeVFSContext               *context);
103 static GnomeVFSResult do_close_directory (GnomeVFSMethod               *method,
104                                           GnomeVFSMethodHandle          *method_handle,
105                                           GnomeVFSContext               *context);
106 static GnomeVFSResult do_read_directory  (GnomeVFSMethod               *method,
107                                           GnomeVFSMethodHandle          *method_handle,
108                                           GnomeVFSFileInfo              *file_info,
109                                           GnomeVFSContext               *context);
110
111 guint                 ftp_connection_uri_hash  (gconstpointer c);
112 gint                  ftp_connection_uri_equal (gconstpointer c, gconstpointer d);
113 static GnomeVFSResult ftp_connection_acquire   (GnomeVFSURI *uri, 
114                                                 FtpConnection **connection, 
115                                                 GnomeVFSContext *context);
116 static void           ftp_connection_release   (FtpConnection *conn);
117
118
119 static const char *anon_user = "anonymous";
120 static const char *anon_pass = "nobody@gnome.org";
121 static const int   control_port = 21;
122
123
124 /* A GHashTable of GLists of FtpConnections */
125 static GHashTable *spare_connections = NULL;
126 G_LOCK_DEFINE_STATIC (spare_connections);
127 static gint total_connections = 0;
128 static gint allocated_connections = 0;
129
130 #if ENABLE_FTP_DEBUG
131
132 #define ftp_debug(c,g) FTP_DEBUG((c),(g),__FILE__, __LINE__, __PRETTY_FUNCTION__)
133 static void 
134 FTP_DEBUG (FtpConnection *conn, 
135            gchar *text, 
136            gchar *file, 
137            gint line, 
138            gchar *func) 
139 {
140         if (conn) {
141                 g_print ("%s:%d (%s) [ftp conn=%p]\n %s\n", file, line, 
142                          func, conn, text);
143         } else {
144                 g_print ("%s:%d (%s) [ftp]\n %s\n", file, line, func, text);
145         }
146
147         g_free (text);
148 }
149
150 #else 
151
152 #define ftp_debug(c,g) (g)
153
154 #endif
155
156 static GnomeVFSResult 
157 ftp_response_to_vfs_result (FtpConnection *conn) 
158 {
159         gint response = conn->response_code;
160
161         switch (response) {
162         case 421: 
163         case 426: 
164           return GNOME_VFS_ERROR_CANCELLED;
165         case 425:
166                 /*FIXME this looks like a bad mapping.  
167                  * 425 is "could not open data connection" which
168                  * probably doesn't have anything to do with file permissions
169                  */ 
170           return GNOME_VFS_ERROR_ACCESS_DENIED;
171         case 530:
172         case 331:
173         case 332:
174         case 532:
175           return GNOME_VFS_ERROR_LOGIN_FAILED;
176         case 450:
177         case 451:
178         case 551:
179           return GNOME_VFS_ERROR_NOT_FOUND;
180         case 550:
181           return conn->fivefifty;
182         case 452:
183         case 552:
184           return GNOME_VFS_ERROR_NO_SPACE;
185         case 553:
186           return GNOME_VFS_ERROR_BAD_FILE;
187         }
188
189         /* is this the correct interpretation of this error? */
190         if (IS_100 (response)) return GNOME_VFS_OK;
191         if (IS_200 (response)) return GNOME_VFS_OK;
192         /* is this the correct interpretation of this error? */
193         if (IS_300 (response)) return GNOME_VFS_OK;
194         if (IS_400 (response)) return GNOME_VFS_ERROR_GENERIC;
195         if (IS_500 (response)) return GNOME_VFS_ERROR_INTERNAL;
196
197         return GNOME_VFS_ERROR_GENERIC;
198
199 }
200
201 static GnomeVFSResult read_response_line(FtpConnection *conn, gchar **line) {
202         GnomeVFSFileSize bytes = MAX_RESPONSE_SIZE, bytes_read;
203         gchar *ptr, *buf = g_malloc (MAX_RESPONSE_SIZE+1);
204         gint line_length;
205         GnomeVFSResult result = GNOME_VFS_OK;
206
207         while (!strstr (conn->response_buffer->str, "\r\n")) {
208                 /* we don't have a full line. Lets read some... */
209                 /*ftp_debug (conn,g_strdup_printf ("response `%s' is incomplete", conn->response_buffer->str));*/
210                 bytes_read = 0;
211                 result = gnome_vfs_socket_buffer_read (conn->socket_buf, buf,
212                                                        bytes, &bytes_read);
213                 buf[bytes_read] = '\0';
214                 /*ftp_debug (conn,g_strdup_printf ("read `%s'", buf));*/
215                 conn->response_buffer = g_string_append (conn->response_buffer,
216                                                          buf);
217                 if (result != GNOME_VFS_OK) {
218                         g_warning ("Error `%s' during read\n", 
219                                    gnome_vfs_result_to_string(result));
220                         g_free (buf);
221                         return result;
222                 }
223         }
224
225         g_free (buf);
226
227         ptr = strstr (conn->response_buffer->str, "\r\n");
228         line_length = ptr - conn->response_buffer->str;
229
230         *line = g_strndup (conn->response_buffer->str, line_length);
231
232         g_string_erase (conn->response_buffer, 0 , line_length + 2);
233
234         return result;
235 }
236
237 static GnomeVFSResult
238 get_response (FtpConnection *conn)
239 {
240         /* all that should be pending is a response to the last command */
241         GnomeVFSResult result;
242
243         /*ftp_debug (conn,g_strdup_printf ("get_response(%p)",  conn));*/
244
245         while (TRUE) {
246                 gchar *line = NULL;
247                 result = read_response_line (conn, &line);
248
249                 if (result != GNOME_VFS_OK) {
250                         g_free (line);
251                         g_warning ("Error reading response line.");
252                         return result;
253                 }
254
255 #ifdef FTP_RESPONSE_DEBUG
256                 g_print ("FTP: %s\n", line);
257 #endif
258
259                 /* response needs to be at least: "### x"  - I think*/
260                 if (g_ascii_isdigit (line[0]) &&
261                     g_ascii_isdigit (line[1]) &&
262                     g_ascii_isdigit (line[2]) &&
263                     g_ascii_isspace (line[3])) {
264
265                         conn->response_code = (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0');
266
267                         if (conn->response_message) g_free (conn->response_message);
268                         conn->response_message = g_strdup (line+4);
269
270 #if 0
271                         ftp_debug (conn,g_strdup_printf ("got response %d (%s)", 
272                                                          conn->response_code, conn->response_message));
273 #endif
274
275                         g_free (line);
276
277                         return ftp_response_to_vfs_result (conn);
278
279                 }
280
281                 /* hmm - not a valid line - lets ignore it :-) */
282                 g_free (line);
283
284         }
285
286         return GNOME_VFS_OK; /* should never be reached */
287
288 }
289
290 static GnomeVFSResult do_control_write (FtpConnection *conn, 
291                                         gchar *command) 
292 {
293         gchar *actual_command = g_strdup_printf ("%s\r\n", command);
294         GnomeVFSFileSize bytes = strlen (actual_command), bytes_written;
295         GnomeVFSResult result = gnome_vfs_socket_buffer_write (conn->socket_buf,
296                                                                actual_command, bytes, &bytes_written);
297 #if 0
298         ftp_debug (conn, g_strdup_printf ("sent \"%s\\r\\n\"", command));
299 #endif
300         gnome_vfs_socket_buffer_flush (conn->socket_buf);
301
302         if(result != GNOME_VFS_OK) {
303                 g_free (actual_command);
304                 return result;
305         }
306
307         if(bytes != bytes_written) {
308                 g_free (actual_command);
309                 return result;
310         }
311
312         g_free (actual_command);
313
314         return result;
315 }
316
317 static GnomeVFSResult 
318 do_basic_command (FtpConnection *conn, 
319                   gchar *command) 
320 {
321         GnomeVFSResult result = do_control_write(conn, command);
322
323         if (result != GNOME_VFS_OK) {
324                 return result;
325         }
326
327         result = get_response (conn);
328
329         return result;
330 }
331
332 static GnomeVFSResult 
333 do_path_command (FtpConnection *conn, 
334                  gchar *command,
335                  GnomeVFSURI *uri) 
336 {
337         char *path;
338         char *actual_command;
339         GnomeVFSResult result;
340
341         /* as some point we may need to make this execute a CD and then
342          * a command using the basename rather than the full path. I am yet
343          * to come across such a system.
344          */
345         path = gnome_vfs_unescape_string (uri->text, G_DIR_SEPARATOR_S);
346
347         if (path == NULL || path[0] == '\0') {
348                 actual_command = g_strconcat (command, " /", NULL);
349         } else {
350                 actual_command = g_strconcat (command, " ", path, NULL);
351         }
352         g_free (path);
353
354         result = do_basic_command (conn, actual_command);
355         g_free (actual_command);
356         return result;
357 }
358
359 static GnomeVFSResult 
360 do_path_command_completely (gchar *command, 
361                             GnomeVFSURI *uri, 
362                             GnomeVFSContext *context,
363                             GnomeVFSResult fivefifty) 
364 {
365         FtpConnection *conn;
366         GnomeVFSResult result;
367
368         result = ftp_connection_acquire (uri, &conn, context);
369         if (result != GNOME_VFS_OK) {
370                 return result;
371         }
372
373         conn->fivefifty = fivefifty;
374         result = do_path_command (conn, command, uri);
375         ftp_connection_release (conn);
376
377         return result;
378 }
379
380 static GnomeVFSResult
381 do_transfer_command (FtpConnection *conn, gchar *command, GnomeVFSContext *context) 
382 {
383         char *host = NULL;
384         gint port;
385         GnomeVFSResult result;
386         GnomeVFSSocket *socket;
387
388         /* Image mode (binary to the uninitiated) */
389         do_basic_command (conn, "TYPE I");
390
391         /* FIXME bugzilla.eazel.com 1464: implement non-PASV mode */
392
393         /* send PASV */
394         do_basic_command (conn, "PASV");
395
396         /* parse response */
397         {
398                 gint a1, a2, a3, a4, p1, p2;
399                 gchar *ptr, *response = g_strdup (conn->response_message);
400                 ptr = strchr (response, '(');
401                 if (!ptr ||
402                     (sscanf (ptr+1,"%d,%d,%d,%d,%d,%d", &a1, &a2, &a3, 
403                              &a4, &p1, &p2) != 6)) {
404                         g_free (response);
405                         return GNOME_VFS_ERROR_CORRUPTED_DATA;
406                 }
407
408                 host = g_strdup_printf ("%d.%d.%d.%d", a1, a2, a3, a4);
409                 port = p1*256 + p2;
410
411                 g_free (response);
412
413         }
414
415         /* connect */
416         result = gnome_vfs_inet_connection_create (&conn->data_connection,
417                                                    host, 
418                                                    port,
419                                                    context ? gnome_vfs_context_get_cancellation(context) : NULL);
420
421         g_free (host);
422         if (result != GNOME_VFS_OK) {
423                 return result;
424         }
425
426         socket = gnome_vfs_inet_connection_to_socket (conn->data_connection);
427         conn->data_socketbuf = gnome_vfs_socket_buffer_new (socket);
428
429         if (conn->socket_buf == NULL) {
430                 gnome_vfs_inet_connection_destroy (conn->data_connection, NULL);
431                 return GNOME_VFS_ERROR_GENERIC;
432         }
433
434         result = do_control_write (conn, command);
435
436         if (result != GNOME_VFS_OK) {
437                 gnome_vfs_socket_buffer_destroy (conn->data_socketbuf, FALSE);
438                 gnome_vfs_inet_connection_destroy (conn->data_connection, NULL);
439                 return result;
440         }
441
442         result = get_response (conn);
443
444         if (result != GNOME_VFS_OK) {
445                 gnome_vfs_socket_buffer_destroy (conn->data_socketbuf, FALSE);
446                 gnome_vfs_inet_connection_destroy (conn->data_connection, NULL);
447                 return result;
448         }
449
450         return result;
451 }
452
453 static GnomeVFSResult
454 do_path_transfer_command (FtpConnection *conn, gchar *command, GnomeVFSURI *uri, GnomeVFSContext *context) 
455 {
456         char *path;
457         char *actual_command;
458         GnomeVFSResult result;
459
460         /* as some point we may need to make this execute a CD and then
461          * a command using the basename rather than the full path. I am yet
462          * to come across such a system.
463          */
464         path = gnome_vfs_unescape_string (uri->text, G_DIR_SEPARATOR_S);
465
466         if (path == NULL || path[0] == '\0') {
467                 actual_command = g_strconcat (command, " /", NULL);
468         } else {
469                 actual_command = g_strconcat (command, " ", path, NULL);
470         }
471         g_free (path);
472
473         result = do_transfer_command (conn, actual_command, context);
474         g_free (actual_command);
475         return result;
476 }
477
478
479 static GnomeVFSResult 
480 end_transfer (FtpConnection *conn) 
481 {
482         GnomeVFSResult result;
483
484         /*ftp_debug (conn, g_strdup ("end_transfer()"));*/
485
486         if(conn->data_socketbuf) {
487                 gnome_vfs_socket_buffer_flush (conn->data_socketbuf);
488                 gnome_vfs_socket_buffer_destroy (conn->data_socketbuf, FALSE);
489                 conn->data_socketbuf = NULL;
490         }
491
492         if (conn->data_connection) {
493                 gnome_vfs_inet_connection_destroy (conn->data_connection, NULL);
494                 conn->data_connection = NULL;
495         }
496
497         result = get_response (conn);
498
499         return result;
500
501 }
502
503
504 static GnomeVFSResult ftp_login (FtpConnection *conn, 
505                                  const char *user, const char *password)
506 {
507         gchar *tmpstring;
508         GnomeVFSResult result;
509         
510         tmpstring = g_strdup_printf ("USER %s", user);
511         result = do_basic_command (conn, tmpstring);
512         g_free (tmpstring);
513
514         if (IS_300 (conn->response_code)) {
515                 tmpstring = g_strdup_printf ("PASS %s", password);
516                 result = do_basic_command (conn, tmpstring);
517                 g_free (tmpstring);
518         }
519
520         return result;
521 }
522
523
524 static GnomeVFSResult 
525 ftp_connection_create (FtpConnection **connptr, GnomeVFSURI *uri, GnomeVFSContext *context) 
526 {
527         FtpConnection *conn = g_new0 (FtpConnection, 1);
528         GnomeVFSResult result;
529         gint port = control_port;
530         const gchar *user = anon_user;
531         const gchar *pass = anon_pass;
532         
533         conn->uri = gnome_vfs_uri_dup (uri);
534         conn->response_buffer = g_string_new ("");
535         conn->response_code = -1;
536         conn->anonymous = TRUE;
537         conn->fivefifty = GNOME_VFS_ERROR_NOT_FOUND;
538
539         if (gnome_vfs_uri_get_host_port (uri)) {
540                 port = gnome_vfs_uri_get_host_port (uri);
541         }
542
543         if (gnome_vfs_uri_get_user_name (uri)) {
544                 user = gnome_vfs_uri_get_user_name (uri);
545                 conn->anonymous = FALSE;
546         }
547
548         if (gnome_vfs_uri_get_password (uri)) {
549                 pass = gnome_vfs_uri_get_password (uri);
550         }
551
552         result = gnome_vfs_inet_connection_create (&conn->inet_connection, 
553                                                    gnome_vfs_uri_get_host_name (uri), 
554                                                    port, 
555                                                    context ? gnome_vfs_context_get_cancellation(context) : NULL);
556         
557         if (result != GNOME_VFS_OK) {
558                 g_warning ("gnome_vfs_inet_connection_create (\"%s\", %d) = \"%s\"",
559                            gnome_vfs_uri_get_host_name (uri),
560                            gnome_vfs_uri_get_host_port (uri),
561                            gnome_vfs_result_to_string (result));
562                 gnome_vfs_uri_unref (conn->uri);
563                 g_string_free (conn->response_buffer, TRUE);
564                 g_free (conn);
565                 return result;
566         }
567
568         conn->socket_buf = gnome_vfs_inet_connection_to_socket_buffer (conn->inet_connection);
569
570         if (conn->socket_buf == NULL) {
571                 g_warning ("Getting socket buffer failed");
572                 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
573                 gnome_vfs_uri_unref (conn->uri);
574                 g_string_free (conn->response_buffer, TRUE);
575                 g_free (conn);
576                 return GNOME_VFS_ERROR_GENERIC;
577         }
578
579         result = get_response (conn);
580
581         if (result != GNOME_VFS_OK) { 
582                 g_warning ("ftp server (%s:%d) said `%d %s'", 
583                            gnome_vfs_uri_get_host_name (uri),
584                            gnome_vfs_uri_get_host_port (uri), 
585                            conn->response_code, conn->response_message);
586                 gnome_vfs_uri_unref (conn->uri);
587                 g_string_free (conn->response_buffer, TRUE);
588                 g_free (conn);
589                 return result;
590         }
591
592         result = ftp_login(conn, user, pass);
593
594         if (result != GNOME_VFS_OK) {
595                 /* login failed */
596                 g_warning ("FTP server said: \"%d %s\"\n", conn->response_code,
597                            conn->response_message);
598                 gnome_vfs_socket_buffer_destroy (conn->socket_buf, FALSE);
599                 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
600                 gnome_vfs_uri_unref (conn->uri);
601                 g_string_free (conn->response_buffer, TRUE);
602                 g_free (conn);
603                         
604                 return result;
605         }
606
607         /* okay, we should be connected now */
608
609         /* Image mode (binary to the uninitiated) */
610
611         do_basic_command (conn, "TYPE I");
612
613         /* Get the system type */
614
615         do_basic_command (conn, "SYST");
616         conn->server_type=g_strdup(conn->response_message);
617
618         *connptr = conn;
619
620         ftp_debug (conn, g_strdup ("created"));
621
622         total_connections++;
623
624         return GNOME_VFS_OK;
625 }
626
627 static void
628 ftp_connection_destroy (FtpConnection *conn) 
629 {
630
631         if (conn->inet_connection) 
632                 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
633
634         if (conn->socket_buf) 
635                 gnome_vfs_socket_buffer_destroy (conn->socket_buf, FALSE);
636
637         gnome_vfs_uri_unref (conn->uri);
638         g_free (conn->cwd);
639
640         if (conn->response_buffer) 
641                 g_string_free(conn->response_buffer, TRUE);
642         g_free (conn->response_message);
643         g_free (conn->server_type);
644
645         if (conn->data_connection) 
646                 gnome_vfs_inet_connection_destroy(conn->data_connection, NULL);
647
648         if (conn->data_socketbuf) 
649                 gnome_vfs_socket_buffer_destroy (conn->data_socketbuf, FALSE);
650
651         g_free (conn->dirlist);
652         g_free (conn->dirlistptr);
653         g_free (conn);
654         total_connections--;
655 }
656
657 /* g_str_hash and g_str_equal don't take null arguments */
658
659 static guint 
660 my_str_hash (const char *c) 
661 {
662         if (c) 
663                 return g_str_hash (c);
664         return 0;
665 }
666
667 static gboolean
668 my_str_equal (const char *c, 
669               const char *d) 
670 {
671         if ((c && !d) || (d &&!c)) 
672                 return FALSE;
673         if (!c && !d) 
674                 return TRUE;
675         return strcmp (c,d) == 0;
676 }
677
678 /* hash the bits of a GnomeVFSURI that distingush FTP connections */
679 guint
680 ftp_connection_uri_hash (gconstpointer c) 
681 {
682         GnomeVFSURI *uri = (GnomeVFSURI *) c;
683
684         return my_str_hash (gnome_vfs_uri_get_host_name (uri)) + 
685                 my_str_hash (gnome_vfs_uri_get_user_name (uri)) +
686                 my_str_hash (gnome_vfs_uri_get_password (uri)) +
687                 gnome_vfs_uri_get_host_port (uri);
688 }
689
690 /* test the equality of the bits of a GnomeVFSURI that distingush FTP 
691  * connections */
692 gint 
693 ftp_connection_uri_equal (gconstpointer c, 
694                           gconstpointer d) 
695 {
696         GnomeVFSURI *uri1 = (GnomeVFSURI *)c;
697         GnomeVFSURI *uri2 = (GnomeVFSURI *) d;
698
699         return my_str_equal (gnome_vfs_uri_get_host_name(uri1),
700                              gnome_vfs_uri_get_host_name (uri2)) &&
701                 my_str_equal (gnome_vfs_uri_get_user_name (uri1),
702                               gnome_vfs_uri_get_user_name (uri2)) &&
703                 my_str_equal (gnome_vfs_uri_get_password (uri1),
704                               gnome_vfs_uri_get_password (uri2)) &&
705                 gnome_vfs_uri_get_host_port (uri1) == 
706                 gnome_vfs_uri_get_host_port (uri2);
707 }
708
709 static GnomeVFSResult 
710 ftp_connection_acquire (GnomeVFSURI *uri, FtpConnection **connection, GnomeVFSContext *context) 
711 {
712         GList *possible_connections;
713         FtpConnection *conn = NULL;
714         GnomeVFSResult result = GNOME_VFS_OK;
715
716         G_LOCK (spare_connections);
717
718         if (spare_connections == NULL) {
719                 spare_connections = g_hash_table_new (ftp_connection_uri_hash, 
720                                                       ftp_connection_uri_equal);
721         }
722
723         possible_connections = g_hash_table_lookup (spare_connections, uri);
724
725         if (possible_connections) {
726                 /* spare connection(s) found */
727                 conn = (FtpConnection *) possible_connections->data;
728 #if 0
729                 ftp_debug (conn, strdup ("found a connection"));
730 #endif
731                 possible_connections = g_list_remove (possible_connections, conn);
732                 g_hash_table_insert (spare_connections, uri, possible_connections);
733
734                 /* make sure connection hasn't timed out */
735                 result = do_basic_command(conn, "PWD");
736                 if (result != GNOME_VFS_OK) {
737                         ftp_connection_destroy (conn);
738                         result = ftp_connection_create (&conn, uri, context);
739                 }
740
741         } else {
742                 result = ftp_connection_create (&conn, uri, context);
743         }
744
745         G_UNLOCK (spare_connections);
746
747         *connection = conn;
748
749         if (result == GNOME_VFS_OK) {
750                 allocated_connections++;
751         }
752
753         return result;
754 }
755
756
757 static void 
758 ftp_connection_release (FtpConnection *conn) 
759 {
760         GList *possible_connections;
761         GnomeVFSURI *uri;
762
763         g_return_if_fail (conn);
764
765         /* reset the 550 result */
766         conn->fivefifty = GNOME_VFS_ERROR_NOT_FOUND;
767
768         G_LOCK (spare_connections);
769         if (spare_connections == NULL) 
770                 spare_connections = 
771                         g_hash_table_new (ftp_connection_uri_hash, 
772                                           ftp_connection_uri_equal);
773
774         possible_connections = g_hash_table_lookup (spare_connections, 
775                                                     conn->uri);
776 #if 0
777         ftp_debug (conn, g_strdup_printf ("releasing [len = %d]", 
778                                           g_list_length (possible_connections)));
779 #endif
780         possible_connections = g_list_append (possible_connections, conn);
781
782         if (g_hash_table_lookup (spare_connections, conn->uri)) {
783                 uri = conn->uri; /* no need to duplicate uri */
784         } else {
785                 /* uri will be used as key */
786                 uri = gnome_vfs_uri_dup (conn->uri); 
787         }
788         g_hash_table_insert (spare_connections, uri, possible_connections);
789         allocated_connections--;
790
791         G_UNLOCK(spare_connections);
792 }
793
794 gboolean 
795 do_is_local (GnomeVFSMethod *method, 
796              const GnomeVFSURI *uri)
797 {
798         return FALSE;
799 }
800
801
802 static GnomeVFSResult 
803 do_open (GnomeVFSMethod *method,
804          GnomeVFSMethodHandle **method_handle,
805          GnomeVFSURI *uri,
806          GnomeVFSOpenMode mode,
807          GnomeVFSContext *context) 
808 {
809         GnomeVFSResult result;
810         FtpConnection *conn;
811
812         result = ftp_connection_acquire (uri, &conn, context);
813         if (result != GNOME_VFS_OK) 
814                 return result;
815
816         if (mode == GNOME_VFS_OPEN_READ) {
817                 conn->operation = FTP_READ;
818                 result = do_path_transfer_command (conn, "RETR", uri, context);
819         } else if (mode == GNOME_VFS_OPEN_WRITE) {
820                 conn->operation = FTP_WRITE;
821                 conn->fivefifty = GNOME_VFS_ERROR_ACCESS_DENIED;
822                 result = do_path_transfer_command (conn, "STOR", uri, context);
823                 conn->fivefifty = GNOME_VFS_ERROR_NOT_FOUND;
824         } else {
825                 g_warning ("Unsupported open mode %d\n", mode);
826                 ftp_connection_release (conn);
827                 return GNOME_VFS_ERROR_INVALID_OPEN_MODE;
828         }
829         if (result == GNOME_VFS_OK) {
830                 *method_handle = (GnomeVFSMethodHandle *) conn;
831         } else {
832                 *method_handle = NULL;
833                 ftp_connection_release (conn);
834         }
835         return result;
836 }
837
838 static GnomeVFSResult
839 do_create (GnomeVFSMethod *method,
840      GnomeVFSMethodHandle **method_handle,
841      GnomeVFSURI *uri,
842      GnomeVFSOpenMode mode,
843      gboolean exclusive,
844      guint perm,
845      GnomeVFSContext *context) {
846         return do_open(method, method_handle, uri, mode, context);
847 }
848
849 static GnomeVFSResult 
850 do_close (GnomeVFSMethod *method,
851           GnomeVFSMethodHandle *method_handle,
852           GnomeVFSContext *context) 
853 {
854         FtpConnection *conn = (FtpConnection *) method_handle;
855
856         GnomeVFSResult result = end_transfer (conn);
857
858         ftp_connection_release (conn);
859
860         return result;
861 }
862
863 static GnomeVFSResult 
864 do_read (GnomeVFSMethod *method, 
865          GnomeVFSMethodHandle *method_handle, 
866          gpointer buffer,
867          GnomeVFSFileSize num_bytes, 
868          GnomeVFSFileSize *bytes_read, 
869          GnomeVFSContext *context) 
870 {
871         FtpConnection *conn = (FtpConnection * )method_handle;
872         GnomeVFSResult result;
873 #if 0
874         /*
875         if (conn->operation != FTP_READ) {
876                 g_print ("attempted to read when conn->operation = %d\n", conn->operation);
877                 return GNOME_VFS_ERROR_NOT_PERMITTED;
878         }*/
879         g_print ("do_read (%p)\n", method_handle);
880 #endif
881
882         result = gnome_vfs_socket_buffer_read (conn->data_socketbuf, buffer, num_bytes, bytes_read);
883
884         if (*bytes_read == 0) {
885                 result = GNOME_VFS_ERROR_EOF;
886         }
887         return result;
888 }
889
890 static GnomeVFSResult 
891 do_write (GnomeVFSMethod *method, 
892           GnomeVFSMethodHandle *method_handle, 
893           gconstpointer buffer, 
894           GnomeVFSFileSize num_bytes, 
895           GnomeVFSFileSize *bytes_written,
896           GnomeVFSContext *context) 
897 {
898         FtpConnection *conn = (FtpConnection *) method_handle;
899         GnomeVFSResult result;
900
901 #if 0
902         g_print ("do_write ()\n");
903 #endif
904
905         if (conn->operation != FTP_WRITE) 
906                 return GNOME_VFS_ERROR_NOT_PERMITTED;
907         
908         result = gnome_vfs_socket_buffer_write (conn->data_socketbuf, buffer, 
909                                                 num_bytes, 
910                                                 bytes_written);
911         return result;
912 }
913
914 /* parse one directory listing from the string pointed to by ls. Parse
915  * only one line from that string. Fill in the appropriate fields of file_info.
916  * return TRUE if a directory entry was found, FALSE otherwise
917  */
918 static gboolean 
919 winnt_ls_to_file_info (gchar *ls, GnomeVFSFileInfo *file_info, 
920                        GnomeVFSFileInfoOptions options) 
921 {
922         char *mtime_str;
923         int m, d, y, h, mn;
924         const char *mime_type;
925
926         /* check parameters */
927         g_return_val_if_fail (file_info != NULL, FALSE);
928
929         /* fill in bits of valid_fields as we go along */
930         file_info->valid_fields = 0;
931
932         /* First 17 chars are DOS date */
933         file_info->mtime = 0;
934         mtime_str = g_strndup (ls, 17);
935         if (sscanf (mtime_str, "%2d-%2d-%2d  %2d:%2d",
936                         &m, &d, &y, &h, &mn) == 5) {
937                 /* yes it's a dos date */
938                 struct tm mtime_parts;
939                 mtime_parts.tm_mon = m - 1;   /* tm_mon is zero-based */
940                 mtime_parts.tm_mday = d;
941                 mtime_parts.tm_year = y >= 70 ? y : y + 100;  /* handle y2k */
942                 mtime_parts.tm_hour = strcasecmp (mtime_str + 15, "pm") == 0 ?
943                                         h + 12 : h;
944                 mtime_parts.tm_min = mn;
945                 mtime_parts.tm_sec = 0;
946                 mtime_parts.tm_isdst = -1;
947                 file_info->mtime = mktime (&mtime_parts);
948                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MTIME;
949         }
950         /* TODO: if there isn't a date it's probably a Unix-style ftp server
951          * running under Windows */
952
953         g_free (mtime_str);
954
955         /* just in case client doesn't check valid_fields */
956         file_info->atime = file_info->mtime;
957         file_info->ctime = file_info->mtime;
958
959         /* filename begins in column 39 */
960         if (strlen (ls) >= 39) {
961                 int i;
962                 i = strcspn (ls + 39, "\r\n");
963                 file_info->name = g_strndup (ls + 39, i);
964         } else {
965                 file_info->name = NULL;
966                 return FALSE;
967         }
968
969         /* if it's a directory, columns 24-29 contains "<DIR>" */
970         file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
971         if (strlen (ls) >= 24) {
972                 char *dirflag_str;
973                 dirflag_str = g_strndup (ls + 24, 5);
974                 if (strcmp (dirflag_str, "<DIR>") == 0) {
975                         file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
976                 }
977                 g_free (dirflag_str);
978         }
979         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE;
980
981         /* if not a directory, we should find right-aligned size ending
982          * at column 37 */
983         if (file_info->type == GNOME_VFS_FILE_TYPE_REGULAR &&
984                         strlen (ls) > 17) {
985                 file_info->size = strtol (ls + 17, NULL, 0);
986                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SIZE;
987         }
988
989         /* mime type */
990         if (file_info->type == GNOME_VFS_FILE_TYPE_REGULAR) {
991                 mime_type = gnome_vfs_mime_type_from_name_or_default (
992                                  file_info->name,
993                                  GNOME_VFS_MIME_TYPE_UNKNOWN);
994         } else {
995                 mime_type = gnome_vfs_mime_type_from_mode (S_IFDIR);
996         }
997         file_info->mime_type = g_strdup (mime_type);
998         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
999
1000         /* fill in other structures with meaningful data, even though
1001          * it may not be valid */
1002         file_info->permissions = GNOME_VFS_PERM_USER_ALL |
1003                                  GNOME_VFS_PERM_GROUP_ALL |
1004                                  GNOME_VFS_PERM_OTHER_ALL;
1005         file_info->flags = GNOME_VFS_FILE_FLAGS_NONE;
1006
1007         return TRUE;
1008 }
1009
1010 /**
1011  * return TRUE if entry found, FALSE otherwise
1012  */
1013 static gboolean 
1014 netware_ls_to_file_info (gchar *ls, GnomeVFSFileInfo *file_info, 
1015                          GnomeVFSFileInfoOptions options) 
1016 {
1017         const char *mime_type;
1018
1019         /* check parameters */
1020         g_return_val_if_fail (file_info != NULL, FALSE);
1021
1022         /* start by knowing nothing */
1023         file_info->valid_fields = 0;
1024
1025         /* If line starts with "total" then we should skip it */
1026         if (strncmp (ls, "total", 5) == 0) {
1027                 return FALSE;
1028         }
1029
1030         /* First char is 'd' for directory, '-' for regular file */
1031         file_info->type = GNOME_VFS_FILE_TYPE_UNKNOWN;
1032         if (strlen (ls) >= 1) {
1033                 if (ls[0] == 'd') {
1034                         file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
1035                 } else if (ls[0] == '-') {
1036                         file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
1037                 } else {
1038                         g_warning ("netware_ls_to_file_info: unknown file type '%c'", ls[0]);
1039                 }
1040         }
1041         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE;
1042     
1043         /* Next is a listing of Netware permissions */
1044         /* ignored */
1045
1046         /* Following the permissions is the "owner/creator" of the file */
1047         /* file info structure requires a UID, which of course is not available */
1048         /* ignored */
1049
1050         /* following type, permissions, and owner is the size, right justified up
1051          * to but not including column 50. */
1052         /* if we start at column 35, that allows 15 chars for size--that should be
1053          * enough :) */
1054         if (strlen (ls) > 35) {
1055                 file_info->size = strtol (ls + 35, NULL, 0);
1056                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SIZE;
1057         }
1058
1059         /* columns 51-63 contain modification date of file/directory */
1060         file_info->mtime = 0;
1061         if (strlen (ls) >= 51) {
1062                 char *mtime_str = g_strndup (ls + 51, 12);
1063                 GDate *mtime_date;
1064
1065                 /* mtime_str is one of two formats...
1066                  *  1)  "mmm dd hh:mm"        (24hr time)
1067                  *  2)  "mmm dd  yyyy"
1068                  */
1069                 mtime_date = g_date_new ();
1070                 if (index (mtime_str, ':') != NULL) {
1071                         /* separate time */
1072                         char *date_str = g_strndup (mtime_str, 6);
1073                         g_date_set_parse (mtime_date, date_str);
1074                         g_free (date_str);
1075                 } else {
1076                         g_date_set_parse (mtime_date, mtime_str);
1077                 }
1078
1079                 if (!g_date_valid (mtime_date)) {
1080                         g_warning ("netware_ls_to_file_info: cannot parse date '%s'",
1081                                    mtime_str);
1082                 }
1083                 else {
1084                         struct tm mtime_parts;
1085                         g_date_to_struct_tm (mtime_date, &mtime_parts);
1086                         mtime_parts.tm_hour = 0;
1087                         mtime_parts.tm_min = 0;
1088                         mtime_parts.tm_sec = 0;
1089                         mtime_parts.tm_isdst = -1;
1090                         if (index (mtime_str, ':')) {
1091                                 /* get the time */
1092                                 int h, mn;
1093                                 if (sscanf (mtime_str + 7, "%2d:%2d", &h, &mn) == 2) {
1094                                         mtime_parts.tm_hour = h;
1095                                         mtime_parts.tm_min = mn;
1096                                 } else {
1097                                         g_warning ("netware_ls_to_file_info: invalid time '%s'",
1098                                                    mtime_str + 7);
1099                                 }
1100                         }
1101                         file_info->mtime = mktime (&mtime_parts);
1102                         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MTIME;
1103                 }
1104
1105                 g_date_free (mtime_date);
1106                 g_free (mtime_str);
1107         }
1108
1109         /* just in case client doesn't check valid_fields */
1110         file_info->atime = file_info->mtime;
1111         file_info->ctime = file_info->mtime;
1112
1113         /* finally, the file/directory name (column 64) */
1114         if (strlen (ls) >= 64) {
1115                 int i;
1116                 i = strcspn (ls + 64, "\r\n");
1117                 file_info->name = g_strndup (ls + 64, i);
1118         } else {
1119                 file_info->name = NULL;
1120         }
1121
1122         /* mime type */
1123         if (file_info->type == GNOME_VFS_FILE_TYPE_REGULAR) {
1124                 mime_type = gnome_vfs_mime_type_from_name_or_default (
1125                                  file_info->name,
1126                                  GNOME_VFS_MIME_TYPE_UNKNOWN);
1127         } else {
1128                 mime_type = gnome_vfs_mime_type_from_mode (S_IFDIR);
1129         }
1130         file_info->mime_type = g_strdup (mime_type);
1131         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
1132
1133         /* fill in other structures with meaningful data, even though
1134          * it may not be valid */
1135         file_info->permissions = GNOME_VFS_PERM_USER_ALL |
1136                                  GNOME_VFS_PERM_GROUP_ALL |
1137                                  GNOME_VFS_PERM_OTHER_ALL;
1138         file_info->flags = GNOME_VFS_FILE_FLAGS_NONE;
1139
1140         return TRUE;
1141 }
1142
1143 static gboolean 
1144 unix_ls_to_file_info (gchar *ls, GnomeVFSFileInfo *file_info, 
1145                       GnomeVFSFileInfoOptions options) 
1146 {
1147         struct stat s;
1148         gchar *filename = NULL, *linkname = NULL;
1149         const char *mime_type;
1150
1151         gnome_vfs_parse_ls_lga (ls, &s, &filename, &linkname);
1152
1153         /* g_print ("filename: %s, linkname: %s\n", filename, linkname); */
1154
1155         if (filename) {
1156
1157                 gnome_vfs_stat_to_file_info (file_info, &s);
1158                 
1159                 /* FIXME: This is a hack, but we can't change
1160                    the above API until after Gnome 1.4.  Ideally, we
1161                    would give the stat_to_file_info function this
1162                    information.  Also, there may be more fields here that are not 
1163                    valid that we haven't dealt with.  */
1164                 file_info->valid_fields |= ~(GNOME_VFS_FILE_INFO_FIELDS_DEVICE
1165                                              | GNOME_VFS_FILE_INFO_FIELDS_INODE
1166                                              | GNOME_VFS_FILE_INFO_FIELDS_IO_BLOCK_SIZE);
1167                 file_info->io_block_size = 0;
1168
1169                 file_info->name = g_path_get_basename (filename);
1170
1171                 if(*(file_info->name) == '\0') {
1172                         g_free (file_info->name);
1173                         file_info->name = g_strdup ("/");
1174                 }
1175
1176                 if(linkname) {
1177                         file_info->symlink_name = linkname;
1178                         file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SYMLINK_NAME;
1179                         file_info->flags |= GNOME_VFS_FILE_FLAGS_SYMLINK;
1180                 }
1181
1182                 if (file_info->type == GNOME_VFS_FILE_TYPE_REGULAR) {
1183                         mime_type = gnome_vfs_mime_type_from_name_or_default (file_info->name, GNOME_VFS_MIME_TYPE_UNKNOWN);
1184                         /*ftp_debug (conn, g_strdup_printf ("mimetype = %s", mime_type));*/
1185                 } else {
1186                         mime_type = gnome_vfs_mime_type_from_mode (s.st_mode);
1187                 }
1188                 file_info->mime_type = g_strdup (mime_type);
1189                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
1190
1191                 /*ftp_debug (conn, g_strdup_printf ("info got name `%s'", file_info->name));*/
1192
1193                 g_free (filename);
1194
1195                 return TRUE;
1196         } else {
1197                 return FALSE;
1198         }
1199 }
1200
1201
1202 #if 0
1203 static GnomeVFSResult
1204 internal_get_file_info (GnomeVFSMethod *method,
1205                         GnomeVFSURI *uri,
1206                         GnomeVFSFileInfo *file_info,
1207                         GnomeVFSFileInfoOptions options,
1208                         GnomeVFSContext *context) 
1209 {
1210         FtpConnection *conn;
1211         /* FIXME bugzilla.eazel.com 1463 */
1212         GnomeVFSResult result;
1213         GnomeVFSFileSize num_bytes = 1024, bytes_read;
1214         gchar buffer[num_bytes+1];
1215
1216         result = ftp_connection_acquire(uri, &conn);
1217         if (result != GNOME_VFS_OK) {
1218                 return result;
1219         }
1220
1221 #if 0
1222         g_print ("do_get_file_info()\n");
1223 #endif
1224
1225         if(strstr(conn->server_type,"MACOS")) {
1226                 /* don't ask for symlinks from MacOS servers */
1227                 do_path_transfer_command (conn, "LIST -ld", uri, context);
1228         } else {
1229                 do_path_transfer_command (conn, "LIST -ldL", uri, context);
1230         }
1231
1232         result = gnome_vfs_socket_buffer_read (conn->data_socketbuf, buffer, 
1233                                                num_bytes, &bytes_read);
1234
1235         if (result != GNOME_VFS_OK) {
1236                 /*ftp_debug (conn, g_strdup ("gnome_vfs_socket_buffer_read failed"));*/
1237                 ftp_connection_release (conn);
1238                 return result;
1239         }
1240
1241         result = end_transfer (conn);
1242
1243         /* FIXME bugzilla.eazel.com 2793: check return? */
1244
1245         ftp_connection_release (conn);
1246
1247         if (result != GNOME_VFS_OK) {
1248                 /*ftp_debug (conn,g_strdup ("LIST for get_file_info failed."));*/
1249                 return result;
1250         }
1251
1252         if (bytes_read>0) {
1253
1254                 buffer[bytes_read] = '\0';
1255                 file_info->valid_fields = 0; /* make sure valid_fields is 0 */
1256
1257                 if (ls_to_file_info (buffer, file_info)) {
1258                         return GNOME_VFS_OK;
1259                 }
1260
1261         }
1262         
1263         return GNOME_VFS_ERROR_NOT_FOUND;
1264
1265 }
1266 #endif
1267
1268 static GnomeVFSResult
1269 do_get_file_info (GnomeVFSMethod *method,
1270                   GnomeVFSURI *uri,
1271                   GnomeVFSFileInfo *file_info,
1272                   GnomeVFSFileInfoOptions options,
1273                   GnomeVFSContext *context) 
1274 {
1275         GnomeVFSURI *parent = gnome_vfs_uri_get_parent (uri);
1276         GnomeVFSResult result;
1277
1278         if (parent == NULL) {
1279                 FtpConnection *conn;
1280                 /* this is a request for info about the root directory */
1281
1282                 /* is the host there? */
1283                 result = ftp_connection_acquire (uri, &conn, context);
1284                 
1285                 if (result != GNOME_VFS_OK) {
1286                         /* doesn't look like it */
1287                         return result;
1288                 }
1289
1290                 ftp_connection_release (conn);
1291
1292                 file_info->name = g_strdup ("/");
1293                 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
1294                 file_info->mime_type = g_strdup ("x-directory/normal");
1295                 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE |
1296                         GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
1297         } else {
1298                 GnomeVFSMethodHandle *method_handle;
1299                 gchar *name;
1300
1301
1302                 name = gnome_vfs_uri_extract_short_name (uri);
1303                 if (name == NULL) {
1304                         gnome_vfs_uri_unref (parent);
1305                         return GNOME_VFS_ERROR_NOT_SUPPORTED;
1306                 }
1307
1308                 result = do_open_directory (method, &method_handle, parent,
1309                                             options, context);
1310
1311                 gnome_vfs_uri_unref (parent);
1312
1313                 if (result != GNOME_VFS_OK) {
1314                         g_free (name);
1315                         return result;
1316                 }
1317
1318                 while (1) {
1319                         result = do_read_directory (method, method_handle, 
1320                                                     file_info, context);
1321                         if (result != GNOME_VFS_OK) {
1322                                 result = GNOME_VFS_ERROR_NOT_FOUND;
1323                                 break;
1324                         }
1325                         if (file_info->name != NULL
1326                             && strcmp (file_info->name, name) == 0) {
1327                                 break;
1328                         }
1329                 }
1330                 g_free (name);
1331                 do_close_directory (method, method_handle, context);
1332         }
1333
1334         return result;
1335 }
1336
1337 static GnomeVFSResult
1338 do_get_file_info_from_handle (GnomeVFSMethod *method,
1339                               GnomeVFSMethodHandle *method_handle,
1340                               GnomeVFSFileInfo *file_info,
1341                               GnomeVFSFileInfoOptions options,
1342                               GnomeVFSContext *context)
1343 {
1344         return do_get_file_info (method,
1345                                  ((FtpConnection *)method_handle)->uri, 
1346                                  file_info, options, context);
1347 }
1348
1349 static GnomeVFSResult
1350 do_open_directory (GnomeVFSMethod *method,
1351                    GnomeVFSMethodHandle **method_handle,
1352                    GnomeVFSURI *uri,
1353                    GnomeVFSFileInfoOptions options,
1354                    GnomeVFSContext *context)
1355 {
1356         FtpConnection *conn;
1357         GnomeVFSResult result;
1358         GnomeVFSFileSize num_bytes = 1024, bytes_read;
1359         gchar buffer[num_bytes+1];
1360         GString *dirlist = g_string_new ("");
1361
1362         result = ftp_connection_acquire (uri, &conn, context);
1363         if (result != GNOME_VFS_OK) {
1364                 g_string_free (dirlist, TRUE);
1365                 return result;
1366         }
1367
1368         /*g_print ("do_open_directory () in uri: %s\n", gnome_vfs_uri_get_path(uri));*/
1369
1370         /* LIST does not return an error if called on a file, but CWD
1371          * should. This allows us to have proper gnome-vfs semantics.
1372          * does the cwd break other things though?  ie, are
1373          * connections stateless?
1374          */
1375         conn->fivefifty = GNOME_VFS_ERROR_NOT_A_DIRECTORY;
1376         result = do_path_command (conn, "CWD", uri);
1377         if (result != GNOME_VFS_OK) {
1378                 ftp_connection_release (conn);
1379                 return result;
1380         }
1381
1382         if(strstr(conn->server_type,"MACOS")) {
1383                 /* don't ask for symlinks from MacOS servers */
1384                 result = do_transfer_command (conn, "LIST", context);
1385         } else {
1386                 result = do_transfer_command (conn, "LIST -L", context);
1387         }
1388
1389         if (result != GNOME_VFS_OK) {
1390                 g_warning ("opendir failed because \"%s\"", 
1391                            gnome_vfs_result_to_string (result));
1392                 ftp_connection_release (conn);
1393                 g_string_free (dirlist, TRUE);
1394                 return result;
1395         }
1396
1397         while (result == GNOME_VFS_OK) {
1398                 result = gnome_vfs_socket_buffer_read (conn->data_socketbuf, buffer, 
1399                                                num_bytes, &bytes_read);
1400                 if (result == GNOME_VFS_OK && bytes_read > 0) {
1401                         buffer[bytes_read] = '\0';
1402                         dirlist = g_string_append (dirlist, buffer);
1403                 } else {
1404                         break;
1405                 }
1406         } 
1407
1408         result = end_transfer (conn);
1409
1410         if(result != GNOME_VFS_OK) g_warning ("end_transfer (conn) failed!!!!");
1411
1412         conn->dirlist = g_strdup (dirlist->str);
1413         conn->dirlistptr = conn->dirlist;
1414         conn->file_info_options = options;
1415
1416         g_string_free (dirlist,TRUE);
1417
1418         *method_handle = (GnomeVFSMethodHandle *) conn;
1419
1420         return result;
1421 }
1422
1423 static GnomeVFSResult
1424 do_close_directory (GnomeVFSMethod *method,
1425                     GnomeVFSMethodHandle *method_handle,
1426                     GnomeVFSContext *context) 
1427 {
1428         FtpConnection *conn = (FtpConnection *) method_handle;
1429
1430 #if 0
1431         g_print ("do_close_directory ()\n");
1432 #endif
1433
1434         g_free (conn->dirlist);
1435         conn->dirlist = NULL;
1436         conn->dirlistptr = NULL;
1437         ftp_connection_release (conn);
1438
1439         return GNOME_VFS_OK;
1440 }
1441
1442 static GnomeVFSResult
1443 do_read_directory (GnomeVFSMethod *method,
1444                    GnomeVFSMethodHandle *method_handle,
1445                    GnomeVFSFileInfo *file_info,
1446                    GnomeVFSContext *context)
1447 {
1448         FtpConnection *conn = (FtpConnection *) method_handle;
1449
1450         if (!conn->dirlistptr || *(conn->dirlistptr) == '\0')
1451                 return GNOME_VFS_ERROR_EOF;
1452
1453         while (TRUE) {
1454                 gboolean success;
1455                 
1456                 if (strncmp (conn->server_type, "Windows_NT", 10) == 0) {
1457                         success = winnt_ls_to_file_info (conn->dirlistptr, file_info,
1458                                                          conn->file_info_options);
1459                 }
1460                 else if (strncmp (conn->server_type, "NETWARE", 7) == 0) {
1461                         success = netware_ls_to_file_info (conn->dirlistptr, file_info,
1462                                                            conn->file_info_options);
1463                 }
1464                 else {
1465                         success = unix_ls_to_file_info (conn->dirlistptr, file_info,
1466                                                         conn->file_info_options);
1467                 }
1468
1469                 /* permissions aren't valid */
1470                 file_info->valid_fields &= ~GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS;
1471
1472                 if ((conn->file_info_options & 
1473                                         GNOME_VFS_FILE_INFO_FOLLOW_LINKS) && 
1474                                 (file_info->type == 
1475                                  GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK)) {
1476                         /* Need to follow symbolic links to match behavior Nautilus
1477                          * requires for sensible display (otherwise symlinks all appear
1478                          * broken)
1479                          */
1480 #if 0
1481                         g_print("[debug] expand symlink for: %s\n", file_info->name);
1482 #endif
1483                 }
1484
1485
1486                 if (*(conn->dirlistptr) == '\0')
1487                         return GNOME_VFS_ERROR_EOF;
1488
1489                 /* go till we find \r\n */
1490                 while (conn->dirlistptr &&
1491                        *conn->dirlistptr && 
1492                        *conn->dirlistptr != '\r' && 
1493                        *conn->dirlistptr != '\n') {
1494                         conn->dirlistptr++;
1495                 }
1496                 /* go past \r\n */
1497                 while (conn->dirlistptr && g_ascii_isspace (*conn->dirlistptr)) {
1498                         conn->dirlistptr++;
1499                 }
1500
1501                 if(success) break;
1502         }
1503
1504         return GNOME_VFS_OK;
1505 }
1506
1507 static GnomeVFSResult
1508 do_check_same_fs (GnomeVFSMethod *method,
1509       GnomeVFSURI *a,
1510       GnomeVFSURI *b,
1511       gboolean *same_fs_return,
1512       GnomeVFSContext *context)
1513 {
1514         *same_fs_return = ftp_connection_uri_equal (a,b);
1515         return GNOME_VFS_OK;
1516 }
1517
1518 static GnomeVFSResult
1519 do_make_directory (GnomeVFSMethod *method, GnomeVFSURI *uri, guint perm, GnomeVFSContext *context)
1520 {
1521         GnomeVFSResult result;
1522         gchar *chmod_command;
1523
1524         result = do_path_command_completely ("MKD", uri, context, 
1525                 GNOME_VFS_ERROR_ACCESS_DENIED);
1526
1527         if (result == GNOME_VFS_OK) {
1528                 /* try to set the permissions */
1529                 /* this is a non-standard extension, so we'll just do our
1530                  * best. We can ignore error codes. */
1531                 chmod_command = g_strdup_printf("SITE CHMOD %o", perm);
1532                 do_path_command_completely (chmod_command, uri, context,
1533                         GNOME_VFS_ERROR_ACCESS_DENIED);
1534                 g_free(chmod_command);
1535         }
1536
1537         return result;
1538 }
1539
1540
1541 static GnomeVFSResult
1542 do_remove_directory (GnomeVFSMethod *method,
1543                      GnomeVFSURI *uri,
1544                      GnomeVFSContext *context)
1545 {
1546         return do_path_command_completely ("RMD", uri, context, 
1547                 GNOME_VFS_ERROR_ACCESS_DENIED);
1548 }
1549
1550
1551 static GnomeVFSResult
1552 do_move (GnomeVFSMethod *method,
1553          GnomeVFSURI *old_uri,
1554          GnomeVFSURI *new_uri,
1555          gboolean force_replace,
1556          GnomeVFSContext *context)
1557 {
1558         GnomeVFSResult result;
1559         GnomeVFSFileInfo *p_file_info;
1560
1561         if (!force_replace) {
1562                 p_file_info = gnome_vfs_file_info_new ();
1563                 result = do_get_file_info (method, new_uri, p_file_info, GNOME_VFS_FILE_INFO_DEFAULT, context);
1564                 gnome_vfs_file_info_unref (p_file_info);
1565                 p_file_info = NULL;
1566
1567                 if (result == GNOME_VFS_OK) {
1568                         return GNOME_VFS_ERROR_FILE_EXISTS;
1569                 }
1570         }
1571         
1572
1573         if (ftp_connection_uri_equal (old_uri, new_uri)) {
1574                 FtpConnection *conn;
1575                 GnomeVFSResult result;
1576
1577                 result = ftp_connection_acquire (old_uri, &conn, context);
1578                 if (result != GNOME_VFS_OK) {
1579                         return result;
1580                 }
1581                 result = do_path_command (conn, "RNFR", old_uri);
1582                 
1583                 if (result == GNOME_VFS_OK) {
1584                         conn->fivefifty = GNOME_VFS_ERROR_ACCESS_DENIED;
1585                         result = do_path_command (conn, "RNTO", new_uri);
1586                         conn->fivefifty = GNOME_VFS_ERROR_NOT_FOUND;
1587                 }
1588
1589                 ftp_connection_release (conn);
1590
1591                 return result;
1592         } else {
1593                 return GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM;
1594         }
1595 }
1596
1597 static GnomeVFSResult
1598 do_unlink (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSContext *context)
1599 {
1600         return do_path_command_completely ("DELE", uri, context,
1601                 GNOME_VFS_ERROR_ACCESS_DENIED);
1602 }
1603
1604 static GnomeVFSResult
1605 do_set_file_info (GnomeVFSMethod *method,
1606                   GnomeVFSURI *uri,
1607                   const GnomeVFSFileInfo *info,
1608                   GnomeVFSSetFileInfoMask mask,
1609                   GnomeVFSContext *context)
1610 {
1611         GnomeVFSURI *parent_uri, *new_uri;
1612         GnomeVFSResult result;
1613
1614         /* FIXME: For now, we only support changing the name. */
1615         if ((mask & ~(GNOME_VFS_SET_FILE_INFO_NAME)) != 0) {
1616                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
1617         }
1618
1619         /* FIXME bugzilla.eazel.com 645: Make sure this returns an
1620          * error for incoming names with "/" characters in them,
1621          * instead of moving the file.
1622          */
1623
1624         /* Share code with do_move. */
1625         parent_uri = gnome_vfs_uri_get_parent (uri);
1626         if (parent_uri == NULL) {
1627                 return GNOME_VFS_ERROR_NOT_FOUND;
1628         }
1629         new_uri = gnome_vfs_uri_append_file_name (parent_uri, info->name);
1630         gnome_vfs_uri_unref (parent_uri);
1631         result = do_move (method, uri, new_uri, FALSE, context);
1632         gnome_vfs_uri_unref (new_uri);
1633         return result;
1634 }
1635
1636 static GnomeVFSMethod method = {
1637         sizeof (GnomeVFSMethod),
1638         do_open,
1639         do_create,
1640         do_close,
1641         do_read,
1642         do_write,
1643         NULL, /* seek */
1644         NULL, /* tell */
1645         NULL, /* truncate */
1646         do_open_directory,
1647         do_close_directory,
1648         do_read_directory,
1649         do_get_file_info,
1650         do_get_file_info_from_handle,
1651         do_is_local,
1652         do_make_directory,
1653         do_remove_directory,
1654         do_move,
1655         do_unlink,
1656         do_check_same_fs,
1657         do_set_file_info,
1658         NULL, /* truncate */
1659         NULL, /* find_directory */
1660         NULL /* create_symbolic_link */
1661 };
1662
1663 GnomeVFSMethod *
1664 vfs_module_init (const char *method_name, 
1665                  const char *args)
1666 {
1667         return &method;
1668 }
1669
1670 void
1671 vfs_module_shutdown (GnomeVFSMethod *method)
1672 {
1673 }