ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / nntp-method.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */
2
3 /* nntp-method.c - VFS module for NNTP
4
5    Copyright (C) 2001 Andy Hertzfeld
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    based on Ian McKellar's (yakk@yakk.net) ftp method for gnome-vfs
23    
24    presents a high level, file-oriented view of a newsgroup, integrating file fragments
25    and organizing them in folders
26
27    Author: Andy Hertzfeld <andy@differnet.com> December 2001
28      
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
34
35 #include <stdlib.h> /* for atoi */
36 #include <stdio.h> /* for sscanf */
37 #include <string.h>
38
39 #include <sys/types.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42
43 #include <glib.h>
44
45 #ifdef HAVE_GCONF
46 #include <gconf/gconf-client.h>
47 #endif
48
49 #include <libgnomevfs/gnome-vfs-context.h>
50 #include <libgnomevfs/gnome-vfs-socket-buffer.h>
51 #include <libgnomevfs/gnome-vfs-inet-connection.h>
52 #include <libgnomevfs/gnome-vfs-method.h>
53 #include <libgnomevfs/gnome-vfs-module.h>
54 #include <libgnomevfs/gnome-vfs-module-shared.h>
55 #include <libgnomevfs/gnome-vfs-module-callback-module-api.h>
56 #include <libgnomevfs/gnome-vfs-standard-callbacks.h>
57 #include <libgnomevfs/gnome-vfs-mime.h>
58 #include <libgnomevfs/gnome-vfs-parse-ls.h>
59 #include <libgnomevfs/gnome-vfs-utils.h>
60
61 #include "nntp-method.h"
62
63
64 #define MAX_RESPONSE_SIZE 4096 
65 #define READ_BUFFER_SIZE 16384
66
67 /* these parameters should eventually be fetched from gconf */
68 #define MAX_MESSAGE_COUNT 2400
69 #define MIN_FILE_SIZE_THRESHOLD 4095
70
71 /* macros for the checking of NNTP response codes */
72 #define IS_100(X) ((X) >= 100 && (X) < 200)
73 #define IS_200(X) ((X) >= 200 && (X) < 300)
74 #define IS_300(X) ((X) >= 300 && (X) < 400)
75 #define IS_400(X) ((X) >= 400 && (X) < 500)
76 #define IS_500(X) ((X) >= 500 && (X) < 600)
77
78 static GnomeVFSResult do_open            (GnomeVFSMethod               *method,
79                                           GnomeVFSMethodHandle         **method_handle,
80                                           GnomeVFSURI                   *uri,
81                                           GnomeVFSOpenMode               mode,
82                                           GnomeVFSContext               *context);
83 static gboolean       do_is_local        (GnomeVFSMethod               *method,
84                                           const GnomeVFSURI             *uri);
85 static GnomeVFSResult do_open_directory  (GnomeVFSMethod                *method,
86                                           GnomeVFSMethodHandle         **method_handle,
87                                           GnomeVFSURI                   *uri,
88                                           GnomeVFSFileInfoOptions        options,
89                                           GnomeVFSContext               *context);
90 static GnomeVFSResult do_close_directory (GnomeVFSMethod               *method,
91                                           GnomeVFSMethodHandle          *method_handle,
92                                           GnomeVFSContext               *context);
93 static GnomeVFSResult do_read_directory  (GnomeVFSMethod               *method,
94                                           GnomeVFSMethodHandle          *method_handle,
95                                           GnomeVFSFileInfo              *file_info,
96                                           GnomeVFSContext               *context);
97
98 guint                 nntp_connection_uri_hash  (gconstpointer c);
99 int                   nntp_connection_uri_equal (gconstpointer c, gconstpointer d);
100 static GnomeVFSResult nntp_connection_acquire   (GnomeVFSURI *uri, 
101                                                 NNTPConnection **connection, 
102                                                 GnomeVFSContext *context);
103 static void           nntp_connection_release   (NNTPConnection *conn);
104
105 static GList* assemble_files_from_overview (NNTPConnection *conn, char *command); 
106
107 static const char *anon_user = "anonymous";
108 static const char *anon_pass = "nobody@gnome.org";
109 static const int   nntp_port = 119;
110
111
112 /* A GHashTable of GLists of NNTPConnections */
113 static GHashTable *spare_connections = NULL;
114 G_LOCK_DEFINE_STATIC (spare_connections);
115 static int total_connections = 0;
116 static int allocated_connections = 0;
117
118 /* single element cache of file objects for a given newsgroup */
119 static char* current_newsgroup_name = NULL;
120 static GList* current_newsgroup_files = NULL;
121
122 static GnomeVFSResult 
123 nntp_response_to_vfs_result (NNTPConnection *conn) 
124 {
125         int response = conn->response_code;
126
127         switch (response) {
128         case 421: 
129         case 426: 
130           return GNOME_VFS_ERROR_CANCELLED;
131         case 425:
132           return GNOME_VFS_ERROR_ACCESS_DENIED;
133         case 530:
134         case 331:
135         case 332:
136         case 532:
137           return GNOME_VFS_ERROR_LOGIN_FAILED;
138         case 450:
139         case 451:
140         case 550:
141         case 551:
142           return GNOME_VFS_ERROR_NOT_FOUND;
143         case 452:
144         case 552:
145           return GNOME_VFS_ERROR_NO_SPACE;
146         case 553:
147           return GNOME_VFS_ERROR_BAD_FILE;
148         }
149
150         /* is this the correct interpretation of this error? */
151         if (IS_100 (response)) return GNOME_VFS_OK;
152         if (IS_200 (response)) return GNOME_VFS_OK;
153         /* is this the correct interpretation of this error? */
154         if (IS_300 (response)) return GNOME_VFS_OK;
155         if (IS_400 (response)) return GNOME_VFS_ERROR_GENERIC;
156         if (IS_500 (response)) return GNOME_VFS_ERROR_INTERNAL;
157
158         return GNOME_VFS_ERROR_GENERIC;
159
160 }
161
162 static GnomeVFSResult read_response_line(NNTPConnection *conn, char **line) {
163         GnomeVFSFileSize bytes = MAX_RESPONSE_SIZE, bytes_read;
164         char *ptr, *buf = g_malloc (MAX_RESPONSE_SIZE+1);
165         int line_length;
166         GnomeVFSResult result = GNOME_VFS_OK;
167
168         while (!strstr (conn->response_buffer->str, "\r\n")) {
169                 /* we don't have a full line. Lets read some... */
170                 bytes_read = 0;
171                 result = gnome_vfs_socket_buffer_read (conn->socketbuf, buf,
172                                                        bytes, &bytes_read);
173                 buf[bytes_read] = '\0';
174                 conn->response_buffer = g_string_append (conn->response_buffer,
175                                                          buf);
176                 if (result != GNOME_VFS_OK) {
177                         g_warning ("Error `%s' during read\n", 
178                                    gnome_vfs_result_to_string(result));
179                         g_free (buf);
180                         return result;
181                 }
182         }
183
184         g_free (buf);
185
186         ptr = strstr (conn->response_buffer->str, "\r\n");
187         line_length = ptr - conn->response_buffer->str;
188
189         *line = g_strndup (conn->response_buffer->str, line_length);
190
191         g_string_erase (conn->response_buffer, 0 , line_length + 2);
192
193         return result;
194 }
195
196
197 static GnomeVFSResult
198 get_response (NNTPConnection *conn)
199 {
200         /* all that should be pending is a response to the last command */
201         GnomeVFSResult result;
202
203         while (TRUE) {
204                 char *line = NULL;
205                 result = read_response_line (conn, &line);
206
207                 if (result != GNOME_VFS_OK) {
208                         g_free (line);
209                         g_warning ("Error reading response line.");
210                         return result;
211                 }
212                 
213                 /* response needs to be at least: "### x"  - I think*/
214                 if (g_ascii_isdigit (line[0]) &&
215                     g_ascii_isdigit (line[1]) &&
216                     g_ascii_isdigit (line[2]) &&
217                     g_ascii_isspace (line[3])) {
218
219                         conn->response_code = g_ascii_digit_value (line[0]) * 100
220                                               + g_ascii_digit_value (line[1]) * 10
221                                               + g_ascii_digit_value (line[2]);
222
223                         if (conn->response_message) g_free (conn->response_message);
224                         conn->response_message = g_strdup (line+4);
225
226                         g_free (line);
227
228                         return nntp_response_to_vfs_result (conn);
229
230                 }
231
232                 /* hmm - not a valid line - lets ignore it :-) */
233                 g_free (line);
234         }
235
236         return GNOME_VFS_OK; /* should never be reached */
237
238 }
239
240 static GnomeVFSResult do_control_write (NNTPConnection *conn, 
241                                         char *command) 
242 {
243         char *actual_command = g_strdup_printf ("%s\r\n", command);
244         GnomeVFSFileSize bytes = strlen (actual_command), bytes_written;
245         GnomeVFSResult result = gnome_vfs_socket_buffer_write (conn->socketbuf,
246                                                                actual_command, bytes, &bytes_written);
247         gnome_vfs_socket_buffer_flush (conn->socketbuf);
248         g_free (actual_command);
249
250         return result;
251 }
252
253 static void
254 nntp_connection_reset_buffer (NNTPConnection *conn)
255 {       
256         g_string_erase (conn->response_buffer, 0, strlen(conn->response_buffer->str));
257 }
258
259 static GnomeVFSResult 
260 do_basic_command (NNTPConnection *conn, 
261                   char *command) 
262 {
263         GnomeVFSResult result;
264
265         nntp_connection_reset_buffer (conn);    
266         
267         result = do_control_write(conn, command);
268
269         if (result != GNOME_VFS_OK) {
270                 return result;
271         }
272
273         result = get_response (conn);
274
275         return result;
276 }
277
278 /* the following routines manage the connection with the news server */
279
280 /* create a new connection */
281 static GnomeVFSResult 
282 nntp_connection_create (NNTPConnection **connptr, GnomeVFSURI *uri, GnomeVFSContext *context) 
283 {
284         NNTPConnection *conn = g_new (NNTPConnection, 1);
285         GnomeVFSResult result;
286         char *tmpstring;
287         int port = nntp_port;
288         const char *user = anon_user;
289         const char *pass = anon_pass;
290         
291         conn->uri = gnome_vfs_uri_dup (uri);
292         
293         conn->response_buffer = g_string_new ("");
294         conn->response_message = NULL;
295         conn->response_code = -1;
296         
297         conn->anonymous = TRUE;
298         
299         /* allocate the read buffer */
300         conn->buffer = g_malloc(READ_BUFFER_SIZE);
301         conn->buffer_size = READ_BUFFER_SIZE;
302         conn->amount_in_buffer = 0;
303         conn->buffer_offset = 0;
304         
305         conn->request_in_progress = FALSE;
306         
307         if (gnome_vfs_uri_get_host_port (uri)) {
308                 port = gnome_vfs_uri_get_host_port (uri);
309         }
310
311         if (gnome_vfs_uri_get_user_name (uri)) {
312                 user = gnome_vfs_uri_get_user_name (uri);
313                 conn->anonymous = FALSE;
314         }
315
316         if (gnome_vfs_uri_get_password (uri)) {
317                 pass = gnome_vfs_uri_get_password (uri);
318         }
319
320         result = gnome_vfs_inet_connection_create (&conn->inet_connection, 
321                                                    gnome_vfs_uri_get_host_name (uri), 
322                                                    port, 
323                                                    context ? gnome_vfs_context_get_cancellation(context) : NULL);
324         
325         if (result != GNOME_VFS_OK) {
326                 g_warning ("gnome_vfs_inet_connection_create (\"%s\", %d) = \"%s\"",
327                            gnome_vfs_uri_get_host_name (uri),
328                            gnome_vfs_uri_get_host_port (uri),
329                            gnome_vfs_result_to_string (result));
330                 g_string_free (conn->response_buffer, TRUE);
331                 g_free (conn);
332                 return result;
333         }
334
335         conn->socketbuf = gnome_vfs_inet_connection_to_socket_buffer (conn->inet_connection);
336
337         if (conn->socketbuf == NULL) {
338                 g_warning ("gnome_vfs_inet_connection_get_socket_buffer () failed");
339                 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
340                 g_string_free (conn->response_buffer, TRUE);
341                 g_free (conn);
342                 return GNOME_VFS_ERROR_GENERIC;
343         }
344
345         result = get_response (conn);
346
347         if (result != GNOME_VFS_OK) { 
348                 g_warning ("nntp server (%s:%d) said `%d %s'", 
349                            gnome_vfs_uri_get_host_name (uri),
350                            gnome_vfs_uri_get_host_port (uri), 
351                            conn->response_code, conn->response_message);
352                 g_string_free (conn->response_buffer, TRUE);
353                 g_free (conn);
354                 return result;
355         }
356
357         if (!conn->anonymous) {
358                 tmpstring = g_strdup_printf ("AUTHINFO user %s", user);
359                 result = do_basic_command (conn, tmpstring);
360                 g_free (tmpstring);
361
362                 if (IS_300 (conn->response_code)) {
363                         tmpstring = g_strdup_printf ("AUTHINFO pass %s", pass);
364                         result = do_basic_command (conn, tmpstring);
365                         g_free (tmpstring);
366                 }
367
368                 if (result != GNOME_VFS_OK) {
369                         /* login failed */
370                         g_warning ("NNTP server said: \"%d %s\"\n", conn->response_code,
371                                 conn->response_message);
372                         gnome_vfs_socket_buffer_destroy (conn->socketbuf, FALSE);
373                         gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
374                         g_free (conn);
375                         return result;
376                 }
377         }
378         *connptr = conn;
379
380         total_connections++;
381
382         /* g_message("successfully logged into server, total connections: %d", total_connections); */
383         return GNOME_VFS_OK;
384 }
385
386 /* destroy the connection object and deallocate the associated resources */
387 static void
388 nntp_connection_destroy (NNTPConnection *conn) 
389 {
390         GnomeVFSResult result;
391         
392         if (conn->inet_connection) { 
393                 result = do_basic_command(conn, "QUIT");      
394                 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
395         }
396         
397         if (conn->socketbuf) 
398                 gnome_vfs_socket_buffer_destroy (conn->socketbuf, FALSE);
399
400         gnome_vfs_uri_unref (conn->uri);
401
402         if (conn->response_buffer) 
403                 g_string_free(conn->response_buffer, TRUE);
404         
405         g_free (conn->response_message);
406         g_free (conn->server_type);
407         g_free (conn->buffer);
408         
409         g_free (conn);
410         total_connections--;
411 }
412
413 /* hash routines for managing the pool of connections */
414 /* g_str_hash and g_str_equal seem to fail with null arguments */
415
416 static int 
417 my_str_hash (const char *c) 
418 {
419         if (c) 
420                 return g_str_hash (c);
421         else return 0;
422 }
423
424 static int 
425 my_str_equal (gconstpointer c, 
426               gconstpointer d) 
427 {
428         if ((c && !d) || (d &&!c)) 
429                 return FALSE;
430         if (!c && !d) 
431                 return TRUE;
432         return g_str_equal (c,d);
433 }
434
435 /* hash the bits of a GnomeVFSURI that distingush NNTP connections */
436 guint
437 nntp_connection_uri_hash (gconstpointer c) 
438 {
439         GnomeVFSURI *uri = (GnomeVFSURI *) c;
440
441         return my_str_hash (gnome_vfs_uri_get_host_name (uri)) + 
442                 my_str_hash (gnome_vfs_uri_get_user_name (uri)) +
443                 my_str_hash (gnome_vfs_uri_get_password (uri)) +
444                 gnome_vfs_uri_get_host_port (uri);
445 }
446
447 /* test the equality of the bits of a GnomeVFSURI that distingush NNTP 
448  * connections
449  */
450 int 
451 nntp_connection_uri_equal (gconstpointer c, 
452                           gconstpointer d) 
453 {
454         GnomeVFSURI *uri1 = (GnomeVFSURI *)c;
455         GnomeVFSURI *uri2 = (GnomeVFSURI *) d;
456
457         return my_str_equal (gnome_vfs_uri_get_host_name(uri1),
458                              gnome_vfs_uri_get_host_name (uri2)) &&
459                 my_str_equal (gnome_vfs_uri_get_user_name (uri1),
460                               gnome_vfs_uri_get_user_name (uri2)) &&
461                 my_str_equal (gnome_vfs_uri_get_password (uri1),
462                               gnome_vfs_uri_get_password (uri2)) &&
463                 gnome_vfs_uri_get_host_port (uri1) == 
464                 gnome_vfs_uri_get_host_port (uri2);
465 }
466
467 static GnomeVFSResult 
468 nntp_connection_acquire (GnomeVFSURI *uri, NNTPConnection **connection, GnomeVFSContext *context) 
469 {
470         GList *possible_connections;
471         NNTPConnection *conn = NULL;
472         GnomeVFSResult result = GNOME_VFS_OK;
473
474         G_LOCK (spare_connections);
475
476         if (spare_connections == NULL) {
477                 spare_connections = g_hash_table_new (nntp_connection_uri_hash, 
478                                                       nntp_connection_uri_equal);
479         }
480
481         possible_connections = g_hash_table_lookup (spare_connections, uri);
482
483         if (possible_connections) {
484                 /* spare connection(s) found */
485                 conn = (NNTPConnection *) possible_connections->data;
486                 possible_connections = g_list_remove (possible_connections, conn);
487
488                 if(!g_hash_table_lookup (spare_connections, uri)) {
489                         /* uri will be used as a key in the hashtable */
490                         uri = gnome_vfs_uri_dup (uri);
491                 }
492
493                 g_hash_table_insert (spare_connections, uri, possible_connections);
494
495                 /* make sure connection hasn't timed out */
496                 result = do_basic_command(conn, "MODE READER");
497                 if (result != GNOME_VFS_OK) {
498                         nntp_connection_destroy (conn);
499                         result = nntp_connection_create (&conn, uri, context);
500                 }
501
502         } else {
503                 result = nntp_connection_create (&conn, uri, context);
504         }
505
506         G_UNLOCK (spare_connections);
507
508         *connection = conn;
509
510         if(result == GNOME_VFS_OK) allocated_connections++;
511         return result;
512 }
513
514
515 static void 
516 nntp_connection_release (NNTPConnection *conn) 
517 {
518         GList *possible_connections;
519         GnomeVFSURI *uri;
520
521         g_return_if_fail (conn);
522
523         G_LOCK (spare_connections);
524         if (spare_connections == NULL) 
525                 spare_connections = 
526                         g_hash_table_new (nntp_connection_uri_hash, 
527                                           nntp_connection_uri_equal);
528
529         possible_connections = g_hash_table_lookup (spare_connections, 
530                                                     conn->uri);
531         possible_connections = g_list_append (possible_connections, conn);
532
533         if (g_hash_table_lookup (spare_connections, conn->uri)) {
534                 uri = conn->uri; /* no need to duplicate uri */
535         } else {
536                 /* uri will be used as key */
537                 uri = gnome_vfs_uri_dup (conn->uri); 
538         }
539         g_hash_table_insert (spare_connections, uri, possible_connections);
540         allocated_connections--;
541
542         G_UNLOCK(spare_connections);
543 }
544
545 /* the following routines are the code for assembling the file list from the newsgroup headers. */
546
547 /* Here are the folder name filtering routines, which attempt to remove track numbers and other extraneous
548  * info from folder names, to make folder grouping work better. First, there are some character classifying
549  * helper routines
550  */
551 static gboolean
552 is_number_or_space (char test_char)
553 {
554         return g_ascii_isspace (test_char) || g_ascii_isdigit (test_char)
555                || test_char == '_' || test_char == '-' || test_char == '/';
556 }
557
558 static gboolean
559 all_numbers_or_spaces (char *left_ptr, char *right_ptr)
560 {
561         char *current_char_ptr;
562         char current_char;
563         
564         current_char_ptr = left_ptr;
565         while (current_char_ptr < right_ptr) {
566                 current_char = *current_char_ptr++;
567                 if (!is_number_or_space(current_char)) {
568                         return FALSE;
569                 } 
570         }
571         return TRUE;
572 }
573
574 /* each of the next set of routines implement a specific filtering operation */
575
576 static void
577 remove_numbers_between_dashes(char *input_str)
578 {
579         char *left_ptr, *right_ptr;
580         int length_to_end, segment_size;
581         
582         left_ptr = strchr (input_str, '-');
583         while (left_ptr != NULL) {
584                 right_ptr = strchr(left_ptr + 1, '-');
585                 if (right_ptr != NULL) {
586                         segment_size = right_ptr - left_ptr;
587                         if (all_numbers_or_spaces(left_ptr, right_ptr) && segment_size > 1) {
588                                 length_to_end = strlen(right_ptr + 1) + 1;
589                                         g_memmove(left_ptr, right_ptr + 1, length_to_end);
590                         } else {
591                                 left_ptr = right_ptr;
592                         }
593                         
594                 } else {
595                         right_ptr = input_str + strlen(input_str) - 1;
596                         if (all_numbers_or_spaces(left_ptr, right_ptr)) {
597                                 left_ptr = '\0';        
598                         }
599                         break;
600                 }
601         }
602 }
603
604
605 static void
606 remove_number_at_end (char *input_str)
607 {
608         char *space_ptr, *next_char;
609         gboolean is_digits;
610         
611         space_ptr = strrchr(input_str, ' ');
612         next_char = space_ptr + 1;
613
614         if (space_ptr != NULL) {
615                 is_digits = TRUE;
616                 while (*next_char != '\0') {
617                         if (!is_number_or_space (*next_char)) {
618                                 is_digits = FALSE;
619                                 break;
620                         }
621                         next_char += 1;
622                 }
623                 if (is_digits) {
624                         *space_ptr = '\0';
625                 }
626         }
627 }
628
629 static char*
630 trim_nonalpha_chars (char *input_str)
631 {
632         char *left_ptr, *right_ptr;
633
634         /* first handle the end of the string */        
635         right_ptr = input_str + strlen(input_str) - 1;
636         while (!g_ascii_isalnum (*right_ptr) && right_ptr > input_str) {
637                 right_ptr -= 1;
638         }
639         
640         right_ptr += 1;
641         *right_ptr = '\0';
642
643         /* now handle the beginning */
644         left_ptr = input_str;
645         while (*left_ptr && !g_ascii_isalnum(*left_ptr)) {
646                 left_ptr++;
647         }
648         return left_ptr;
649 }
650
651 static void
652 remove_of_expressions (char *input_str)
653 {
654         char *left_ptr, *right_ptr;
655         int length_to_end;
656         gboolean found_number;
657         
658         left_ptr = strstr (input_str, "of");
659         if (left_ptr == NULL) {
660                 left_ptr = strstr (input_str, "OF");
661         }
662         if (left_ptr == NULL) {
663                 left_ptr = strstr (input_str, "/");
664         }
665         
666         if (left_ptr != NULL) {
667                 found_number = FALSE;
668                 right_ptr = left_ptr + 2;
669                 left_ptr = left_ptr - 1;
670                 while (is_number_or_space(*left_ptr) && left_ptr >= input_str) {
671                         found_number = found_number || g_ascii_isdigit(*left_ptr);
672                         left_ptr -= 1;
673                 }
674                 while (is_number_or_space(*right_ptr)) {
675                         found_number = found_number || g_ascii_isdigit(*right_ptr);
676                         right_ptr += 1;
677                 }
678                 
679                 if (found_number) {             
680                         length_to_end = strlen(right_ptr);
681                         if (length_to_end > 0) {
682                                 g_memmove(left_ptr + 1, right_ptr, length_to_end + 1);
683                         } else {
684                                 left_ptr += 1;
685                                 *left_ptr = '\0';
686                         }
687                 }
688         }
689 }
690
691 /* here is the main folder name filtering routine, which returns a new string containing the filtered
692  * version of the passed-in folder name
693  */
694 static char *
695 filter_folder_name(char *folder_name)
696 {
697         char *temp_str, *save_str, *result_str;
698         char *colon_ptr, *left_ptr, *right_ptr;
699         int length_to_end;
700         
701         temp_str = g_strdup(folder_name);
702         save_str = temp_str;
703         temp_str = g_strstrip(temp_str);
704         
705         /* if there is a colon, strip everything before it */
706         colon_ptr = strchr(temp_str, ':');
707         if (colon_ptr != NULL) {
708                 temp_str = colon_ptr + 1;
709         }
710         
711         /* remove everything in brackets */
712         left_ptr = strrchr(temp_str, '[');
713         if (left_ptr != NULL) {
714                 right_ptr = strchr(left_ptr, ']');
715                 if (right_ptr != NULL && right_ptr > left_ptr) {
716                         length_to_end = strlen(right_ptr + 1) + 1;
717                         g_memmove(left_ptr, right_ptr + 1, length_to_end);
718                 }       
719         }
720         
721         /* eliminate "n of m" expressions */
722         remove_of_expressions(temp_str);
723                 
724         /* remove numbers at end of the string */
725         remove_number_at_end(temp_str);
726         
727         /* remove numbers between dashes */
728         remove_numbers_between_dashes(temp_str);
729         
730         /* trim leading and trailing non-alphanumeric characters */
731         temp_str = trim_nonalpha_chars(temp_str);
732         
733         /* truncate if necessary */
734         if (strlen(temp_str) > 30) {
735                 left_ptr = temp_str + 29;
736                 while (g_ascii_isalpha(*left_ptr)) {
737                         left_ptr += 1;
738                 }
739                 
740                 *left_ptr = '\0';
741         }               
742         
743         /* return the result */
744         result_str = g_strdup(temp_str);
745         g_free(save_str);
746         return result_str;
747 }
748
749 /* parse dates by allowing gnome-vfs to do most of the work */
750
751 static void
752 remove_commas(char *source_str)
753 {
754         char *ptr;
755         
756         ptr = source_str;
757         while (*ptr != '\0') {
758                 if (*ptr == ',') {
759                         g_memmove(ptr, ptr+1, strlen(ptr));
760                 } else {
761                         ptr += 1;
762                 }
763         }
764 }
765
766 static gboolean
767 is_number(char *input_str) {
768         char *cur_char;
769         
770         cur_char = input_str;
771         while (*cur_char) {
772                 if (!g_ascii_isdigit(*cur_char)) {
773                         return FALSE;
774                 }
775                 cur_char += 1;
776         }
777         return TRUE;
778 }
779
780 static void
781 parse_date_string (const char* date_string, time_t *t)
782 {
783         struct stat s;
784         char *filename, *linkname;
785         char *temp_str, *mapped_date;
786         gboolean result;
787         char **date_parts;
788         int offset;
789         
790         filename = NULL;
791         linkname = NULL;
792         
793         /* remove commas from day, and flip the day and month */
794         date_parts = g_strsplit(date_string, " ", 0);
795         
796         if (is_number(date_parts[0])) {
797                 offset = 0;
798         } else {
799                 offset = 1;
800                 remove_commas(date_parts[0]);
801         }
802         
803         temp_str = date_parts[offset];
804         date_parts[offset] = date_parts[offset + 1];
805         date_parts[offset + 1] = temp_str;
806         mapped_date = g_strjoinv(" ", date_parts);
807         
808         temp_str = g_strdup_printf("-rw-rw-rw- 1 500 500 %s x", mapped_date);
809         g_strfreev(date_parts);
810         g_free (mapped_date);
811         
812         result = gnome_vfs_parse_ls_lga (temp_str, &s, &filename, &linkname);
813         if (!result) {
814                 g_message("error parsing %s, %s", date_string, temp_str); 
815         }
816         
817         
818         g_free(temp_str);
819         *t = s.st_mtime;
820 }
821
822 /* parse_header takes a message header an extracts the subject and id, and then
823  * parses the subject to get the file name and fragment sequence number
824  */
825 static gboolean
826 parse_header(const char* header_str, char** filename, char** folder_name, char** message_id,
827                 int* message_size, int* part_number, int* total_parts, time_t *mod_date)
828 {
829         char* temp_str, *file_start;
830         char* left_paren, *right_paren, *slash_pos;
831         int slash_bump;
832         
833         char **header_parts;
834         gboolean has_count;
835         
836         *part_number = -1;
837         *total_parts = -1;
838         *message_size = 0;
839         
840         *filename = NULL;
841         *folder_name = NULL;
842         *message_id = NULL;
843         slash_pos = NULL;
844                 
845         header_parts = g_strsplit(header_str, "\t", 0);
846         temp_str = g_strdup(header_parts[1]);
847         
848         *message_id = g_strdup(header_parts[4]);
849  
850         if (header_parts[6] != NULL) {
851                 *message_size = atoi(header_parts[6]);
852         }
853  
854         parse_date_string(header_parts[3], mod_date);
855                 
856         g_strfreev(header_parts);
857         
858         /* find the parentheses and extra the part number and total part count */       
859         
860         has_count = FALSE;
861         left_paren = strchr(temp_str,'(');
862         right_paren = strchr(temp_str, ')');
863         
864         /* if we could't find parentheses, try braces */
865         if (left_paren == NULL) {
866                 left_paren = strchr(temp_str,'[');
867                 right_paren = strchr(temp_str, ']');
868         }
869         
870         while (!has_count && left_paren != NULL && right_paren != NULL) {
871                 slash_pos = strchr(left_paren, '/');
872                 slash_bump = 1;
873                 
874                 if (slash_pos == NULL) {
875                         slash_pos = strchr(left_paren, '-');
876                 }
877                 if (slash_pos == NULL) {
878                         slash_pos = strstr(left_paren, " of ");
879                         slash_bump = 4;
880                 }
881                 
882                 if (slash_pos != NULL) {
883                         has_count = TRUE;
884                         *slash_pos = '\0';
885                         *right_paren = '\0';
886
887                         *part_number = atoi (left_paren + 1);
888                         *total_parts = atoi (slash_pos + slash_bump);
889                 } else {
890                         left_paren = strchr(right_paren + 1, '(');
891                         right_paren = strchr(right_paren + 1, ')');
892                 }
893         }
894         
895         if (!has_count) {       
896                 *part_number = 1;
897                 *total_parts = 1;
898         }
899         
900         
901         /* isolate the filename and copy it to the result */
902         if (has_count) {
903                 *left_paren = '\0';
904                 file_start = strrchr (temp_str, '-');
905         
906                 if (file_start == NULL) {
907                         return FALSE;
908                 }
909         
910                 *filename = g_strdup (g_strstrip (file_start + 1));
911                 *file_start = '\0';
912                 *folder_name = filter_folder_name (temp_str);
913         } else
914                 {
915                 *filename = g_strdup (temp_str);
916         }
917         
918         g_free(temp_str);
919         return TRUE;
920 }
921
922
923 /* create a new file fragment structure */
924 static nntp_fragment*
925 nntp_fragment_new(int fragment_number, char* fragment_id, int fragment_size)
926 {
927         nntp_fragment* new_fragment = (nntp_fragment*) g_malloc (sizeof (nntp_fragment));
928         new_fragment->fragment_number = fragment_number;
929         new_fragment->fragment_id = g_strdup (fragment_id);
930         new_fragment->fragment_size = fragment_size;
931         new_fragment->bytes_read = 0;
932         
933         return new_fragment;
934 }
935
936 /* deallocate the passed in file fragment */
937 static void
938 nntp_fragment_destroy(nntp_fragment* fragment)
939 {
940         g_free(fragment->fragment_id);
941         g_free(fragment);
942 }
943
944 /* utility routine to map slashes to dashes in the passed in string so we never return
945  * a filename with slashes
946  */
947 static char *
948 map_slashes(char *str)
949 {
950         char *current_ptr;
951         
952         current_ptr = str;
953         while (*current_ptr != '\0') {
954                 if (*current_ptr == '/') {
955                         *current_ptr = '-';
956                 }
957                 current_ptr += 1;
958         }
959         return str;
960 }
961
962 /* allocate a file object */
963 static nntp_file*
964 nntp_file_new(char* file_name, char *folder_name, int total_parts)
965 {
966         
967         nntp_file* new_file = (nntp_file*) g_malloc (sizeof (nntp_file));
968
969         if (strlen(map_slashes(file_name)) < 1) {
970                 file_name = "(Empty)";
971         }
972         
973         new_file->file_name = map_slashes(g_strdup(file_name));
974         new_file->folder_name = g_strdup(folder_name);
975         
976         new_file->file_type = NULL;
977         new_file->part_list = NULL;
978         
979         new_file->file_size = 0;
980         new_file->total_parts = total_parts;
981         new_file->is_directory = FALSE;
982         
983         return new_file;
984 }
985
986 /* deallocate a file object */
987 static void
988 nntp_file_destroy (nntp_file* file)
989 {
990         GList *current_part;
991         
992         g_free(file->file_name);
993         g_free(file->folder_name);
994         
995         current_part = file->part_list;
996         while (current_part != NULL) {
997                 if (file->is_directory) {
998                         nntp_file_destroy((nntp_file*) current_part->data);
999                 } else {
1000                         nntp_fragment_destroy((nntp_fragment*) current_part->data);
1001                 }
1002                 current_part = current_part->next;
1003         }
1004         
1005         g_list_free(file->part_list);
1006         g_free(file);
1007 }
1008
1009
1010 /* look up a fragment in a file */
1011 static nntp_fragment*
1012 nntp_file_look_up_fragment (nntp_file *file, int fragment_number)
1013 {
1014         nntp_fragment *fragment;
1015         GList *current_fragment = file->part_list;
1016         while (current_fragment != NULL) {
1017                 fragment = (nntp_fragment*) current_fragment->data;
1018                 if (fragment->fragment_number == fragment_number) {
1019                         return fragment;
1020                 }
1021                 current_fragment = current_fragment->next;
1022         }
1023         return NULL;    
1024 }
1025
1026 /* compute the total size of a file by adding up the size of its fragments, then scale down by 3/4 to account for
1027  * the uu or base64 encoding
1028  */
1029 static int
1030 nntp_file_get_total_size (nntp_file *file)
1031 {
1032         nntp_fragment *fragment;
1033         int total_size;
1034         GList *current_fragment;
1035         
1036         total_size = 0;
1037         current_fragment = file->part_list;     
1038         while (current_fragment != NULL) {
1039                 fragment = (nntp_fragment*) current_fragment->data;
1040                 total_size += fragment->fragment_size - 800; /* subtract out average header size */
1041                 
1042                 current_fragment = current_fragment->next;
1043         }
1044         
1045         return 3 * total_size / 4;      
1046 }
1047
1048 /* add a fragment to a file */
1049 static void
1050 nntp_file_add_part (nntp_file *file, int fragment_number, char* fragment_id, int fragment_size)
1051 {
1052         /* make sure we don't already have this fragment */
1053         /* note: this isn't good for threads, where more than one message has the same subject line,
1054          * so we really should collect them instead of discarding all but the first */
1055          
1056         if (nntp_file_look_up_fragment (file, fragment_number) == NULL) {
1057                 nntp_fragment* fragment = nntp_fragment_new (fragment_number, fragment_id, fragment_size);
1058                 file->part_list = g_list_append (file->part_list, fragment);
1059         }
1060 }
1061
1062
1063 /* look up a file object in the file list from its name; avoid lookup if same as last time (soon) */
1064 static nntp_file*
1065 look_up_file (GList *file_list, char *filename, gboolean is_directory)
1066 {
1067         nntp_file* file;
1068         GList* current_file = file_list;
1069         while (current_file != NULL) {
1070                 file = (nntp_file*) current_file->data;
1071                 if (g_ascii_strcasecmp( file->file_name, filename) == 0 && 
1072                     file->is_directory == is_directory) {
1073                         return file;
1074                 }
1075                 current_file = current_file->next;
1076         }
1077         return NULL;
1078 }
1079
1080 /* start reading the article associated with the passed in fragment */
1081 static GnomeVFSResult
1082 start_loading_article (NNTPConnection *conn, nntp_fragment *fragment)
1083 {
1084         GnomeVFSResult result;
1085         char *command_str;
1086         char *line = NULL;
1087         
1088         /* issue the command to fetch the article */
1089         command_str = g_strdup_printf("BODY %s", fragment->fragment_id);
1090         result = do_control_write(conn, command_str);   
1091         g_free(command_str);
1092         
1093         if (result != GNOME_VFS_OK) {
1094                 return result;
1095         }
1096         
1097         /* read the response command, and check that it's the right number (eventually) */
1098         result = read_response_line (conn, &line);      
1099         g_free(line);
1100         if (result != GNOME_VFS_OK) {
1101                 return result;
1102         }
1103         
1104         conn->request_in_progress = TRUE;
1105         return GNOME_VFS_OK;
1106 }
1107
1108 /* utility routine to uudecode the passed in text.  Translate every four 6-bit bytes to 3 8 bit ones */
1109 static int
1110 uu_decode_text(char *text_buffer, int text_len)
1111 {
1112         int input_index, output_index;
1113         int byte_0, byte_1, byte_2, byte_3;
1114         
1115         input_index = 1; /* skip length byte */
1116         output_index = 0;
1117         while (input_index < text_len) {
1118                 byte_0 = text_buffer[input_index] - 32;
1119                 byte_1 = text_buffer[input_index + 1] - 32;
1120                 byte_2 = text_buffer[input_index + 2] - 32;
1121                 byte_3 = text_buffer[input_index + 3] - 32;
1122
1123                 text_buffer[output_index] = ((byte_0 << 2) & 252) | ((byte_1 >> 4) & 3);                
1124                 text_buffer[output_index + 1] = ((byte_1 << 4) & 240) | ((byte_2 >> 2) & 15);           
1125                 text_buffer[output_index + 2] = ((byte_2 << 6) & 192) | (byte_3 & 63);          
1126         
1127                 input_index += 4;
1128                 output_index += 3;
1129         }
1130         return output_index;
1131 }
1132
1133 /* utility to decode the passed in text using base 64 */
1134
1135 static int
1136 base_64_map(char input_char)
1137 {
1138         if (input_char >= 'A' && input_char <= 'Z') {
1139                 return input_char - 'A';
1140         }
1141         
1142         if (input_char >= 'a' && input_char <= 'z') {
1143                 return 26 + input_char - 'a';
1144         }
1145         
1146         if (input_char >= '0' && input_char <= '9') {
1147                 return 52 + input_char - '0';
1148         }
1149         
1150         if (input_char == '+') {
1151                 return 62;
1152         }
1153         
1154         if (input_char == '/') {
1155                 return 63;
1156         }
1157         
1158         if (input_char == '=') {
1159                 return 0;
1160         }
1161         
1162         /* not a valid character, so return -1 */
1163         return -1;      
1164 }
1165
1166 static int
1167 base_64_decode_text(char* text_buffer, int text_len)
1168 {
1169         int input_index, output_index;
1170         int byte_0, byte_1, byte_2, byte_3;
1171         
1172         input_index = 1; /* skip length byte */
1173         output_index = 0;
1174         while (input_index < text_len) {
1175                 byte_0 = base_64_map(text_buffer[input_index]);
1176                 byte_1 = base_64_map(text_buffer[input_index + 1]);
1177                 byte_2 = base_64_map(text_buffer[input_index + 2]);
1178                 byte_3 = base_64_map(text_buffer[input_index + 3]);
1179
1180                 /* if we hit a return, we're done */
1181                 if (text_buffer[input_index] < 32) {
1182                         return output_index;
1183                 }
1184                 
1185                 /* if there are any unmappable characters, skip this line */
1186                 if (byte_0 < 0 || byte_1 < 0 || byte_2 < 0 || byte_3 < 0) {
1187                         return 0;
1188                 }
1189                 
1190                 /* shift the bits into place and output them */
1191                 text_buffer[output_index] = ((byte_0 << 2) & 252) | ((byte_1 >> 4) & 3);                
1192                 text_buffer[output_index + 1] = ((byte_1 << 4) & 240) | ((byte_2 >> 2) & 15);           
1193                 text_buffer[output_index + 2] = ((byte_2 << 6) & 192) | (byte_3 & 63);          
1194         
1195                 input_index += 4;
1196                 output_index += 3;
1197         }
1198         return output_index;
1199 }
1200
1201 /* utility that returns true if all the characters in the passed in line are valide for uudecoding */
1202 static gboolean
1203 line_in_decode_range(char* input_line)
1204 {
1205         char *line_ptr;
1206         char current_char;
1207         
1208         line_ptr = input_line;
1209         while (*line_ptr != '\0') {
1210                 current_char = *line_ptr++;
1211                 if (current_char < 32 || current_char > 95) {
1212                         return FALSE;   
1213                 }
1214         }
1215         return TRUE;
1216 }
1217
1218 /* fill the buffer from the passed in article fragment.  If the first line flag is set,
1219  * test to see if uudecoding is required.
1220  */
1221 static GnomeVFSResult
1222 load_from_article (NNTPConnection *conn, nntp_fragment *fragment, gboolean first_line_flag)
1223 {
1224         GnomeVFSResult result;
1225         char *line = NULL;
1226         char *dest_ptr;
1227         int buffer_offset;
1228         int line_len;
1229                         
1230         /* loop, loading the article into the buffer */
1231         buffer_offset = 0;
1232         
1233         while (buffer_offset < conn->buffer_size - 1024) {
1234                 result = read_response_line (conn, &line);      
1235                 if (first_line_flag && !conn->uu_decode_mode && !conn->base_64_decode_mode) {
1236                         /* FIXME: should apply additional checks here for the permission flags, etc */                  
1237                         if (strncmp(line, "begin ", 6) == 0) {
1238                                 conn->uu_decode_mode = TRUE;
1239                                 g_free (line);
1240                                 buffer_offset = 0;
1241                                 continue;
1242                         } else if (strncmp(line, "Content-Transfer-Encoding: base64", 33) == 0) {
1243                                 conn->base_64_decode_mode = TRUE;
1244                                 g_free (line);
1245                                 buffer_offset = 0;
1246                                 continue;
1247                         } else if (line[0] == 'M' && strlen(line) == 61 && line_in_decode_range(line)) {
1248                                 conn->uu_decode_mode = TRUE;
1249                                 buffer_offset = 0;
1250                         }
1251
1252                 }
1253                 
1254                 if (line[0] != '.' && line[1] != '\r') {
1255                         line_len = strlen(line);
1256                         if (buffer_offset + line_len > conn->buffer_size) {
1257                                 g_message("Error! exceeded buffer! %d", buffer_offset + line_len);
1258                                 line_len = conn->buffer_size - buffer_offset;
1259                         }
1260                         dest_ptr = (char*) conn->buffer + buffer_offset;
1261                         g_memmove(dest_ptr, line, line_len);
1262                         if (conn->uu_decode_mode) {
1263                                 line_len = uu_decode_text(dest_ptr, line_len);
1264                                 buffer_offset += line_len;
1265                                 fragment->bytes_read += line_len;
1266                         } else if (conn->base_64_decode_mode) {
1267                                 line_len = base_64_decode_text(dest_ptr, line_len);
1268                                 buffer_offset += line_len;
1269                                 fragment->bytes_read += line_len;
1270                         } else {
1271                                 buffer_offset += line_len + 1;
1272                                 dest_ptr += line_len;
1273                                 *dest_ptr = '\n';
1274                                 fragment->bytes_read += line_len + 1;
1275                         }
1276                 } else {
1277                         g_free(line);
1278                         conn->request_in_progress = FALSE;
1279                         break;
1280                 }
1281                 g_free(line);
1282         }
1283         conn->amount_in_buffer = buffer_offset;
1284         conn->buffer_offset = 0;
1285         
1286         return GNOME_VFS_OK;
1287 }
1288
1289 /* load data from the current file fragment, advancing to a new one if necessary */
1290 static GnomeVFSResult
1291 load_file_fragment(NNTPConnection *connection)
1292 {
1293         nntp_fragment *fragment;
1294         GnomeVFSResult result;
1295         gboolean first_line_flag;
1296         
1297         first_line_flag = FALSE;
1298         if (!connection->request_in_progress) {
1299                 if (connection->current_fragment == NULL) {
1300                         connection->current_fragment = connection->current_file->part_list;
1301                         first_line_flag = TRUE;
1302                 } else {
1303                         connection->current_fragment = connection->current_fragment->next;
1304                         if (connection->current_fragment == NULL) {
1305                                 connection->eof_flag = TRUE;
1306                                 return GNOME_VFS_ERROR_EOF;
1307                         }
1308                 }
1309                 fragment = (nntp_fragment *) connection->current_fragment->data;        
1310                 result = start_loading_article(connection, fragment);
1311         }
1312         
1313         if (connection->current_fragment == NULL) {
1314                 connection->eof_flag = TRUE;
1315                 return GNOME_VFS_ERROR_EOF;
1316         }
1317         
1318         fragment = (nntp_fragment *) connection->current_fragment->data;        
1319         result = load_from_article(connection, fragment, first_line_flag);
1320         return result;
1321 }
1322
1323 /* calculate the size of the data remaining in the buffer */
1324 static int
1325 bytes_in_buffer(NNTPConnection *connection)
1326 {
1327         return connection->amount_in_buffer - connection->buffer_offset;
1328 }
1329
1330 /* utility routine to move bytes from the fragment to the application */
1331 static int
1332 copy_bytes_from_buffer(NNTPConnection *connection,
1333                         gpointer destination_buffer,
1334                         int bytes_to_read,
1335                         GnomeVFSFileSize *bytes_read)
1336 {
1337         int size_to_move;
1338         
1339         size_to_move = bytes_in_buffer(connection);
1340         if (size_to_move == 0) {
1341                 return 0;
1342         }
1343         
1344         if (bytes_to_read < size_to_move) {
1345                 size_to_move = bytes_to_read;
1346         }
1347         /* move the bytes from the buffer */
1348         g_memmove(destination_buffer, ((char*) connection->buffer) + connection->buffer_offset, size_to_move);
1349         
1350         /* update the counts */
1351         connection->buffer_offset += size_to_move;
1352         *bytes_read += size_to_move;
1353         return size_to_move;
1354 }
1355
1356 /* read a byte range from the active connection */
1357 static GnomeVFSResult
1358 nntp_file_read(NNTPConnection *connection,
1359                 gpointer buffer,
1360                 GnomeVFSFileSize num_bytes,
1361                 GnomeVFSFileSize *bytes_read) 
1362 {       
1363         int bytes_to_read;
1364         GnomeVFSResult result;
1365         
1366         /* loop, loading fragments and copying data until the request is fulfilled or
1367          * we're out of fragments
1368          */
1369         bytes_to_read = num_bytes;
1370         *bytes_read = 0;
1371         while (bytes_to_read > 0) {
1372                 bytes_to_read -= copy_bytes_from_buffer(connection, ((char*) buffer) + *bytes_read, bytes_to_read, bytes_read);
1373                 if (bytes_to_read > bytes_in_buffer(connection)) {
1374                         if (connection->eof_flag) {
1375                                 /* don't return EOF here as it will cause the last part to be discarded */
1376                                 return GNOME_VFS_OK;
1377                         }
1378                         result = load_file_fragment(connection);
1379                 }
1380         }       
1381         return GNOME_VFS_OK;
1382 }
1383
1384 /* deallocate the passed in file list */
1385 static void
1386 free_nntp_file_list (GList *file_list)
1387 {
1388         GList* current_file;
1389         if (file_list == NULL) {
1390                 return;
1391         }
1392         
1393         current_file = file_list;
1394         while (current_file != NULL) {
1395                 nntp_file_destroy ((nntp_file*) current_file->data);
1396                 current_file = current_file->next;
1397         }
1398         g_list_free (file_list);
1399 }
1400
1401 /* utility to obtain authorization info from gnome-vfs */
1402 static void
1403 get_auth_info (NNTPConnection *conn, char** user, char** password)
1404 {
1405         GnomeVFSResult res;
1406         GnomeVFSModuleCallbackAuthenticationIn in_args;
1407         GnomeVFSModuleCallbackAuthenticationOut out_args;
1408         
1409         *user = NULL;
1410         *password = NULL;
1411         
1412         memset (&in_args, 0, sizeof (in_args));                 
1413         memset (&out_args, 0, sizeof (out_args));
1414         
1415         in_args.uri = gnome_vfs_uri_to_string(conn->uri, 0);                    
1416         res = gnome_vfs_module_callback_invoke (GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION,
1417                                                 &in_args, sizeof (in_args), 
1418                                                         &out_args, sizeof (out_args));  
1419         g_free(in_args.uri);
1420                                                 
1421         *user = out_args.username;
1422         *password = out_args.password;
1423 }
1424
1425 /* key routine to load the newsgroup overview and return a list of nntp_file objects.
1426  * It maintains a cache to avoid reloading.
1427  *
1428  * FIXME: for now, we use a single element cache, but eventually we want to cache multiple newsgroups
1429  * also, we eventually want to reload it if enough time has passed
1430  */
1431 static GnomeVFSResult
1432 get_files_from_newsgroup (NNTPConnection *conn, const char* newsgroup_name, GList** result_file_list)
1433 {
1434         GnomeVFSResult result;
1435         char *group_command, *tmpstring;
1436         int first_message, last_message, total_messages;
1437         char *command_str;
1438         GList *file_list;
1439         gchar *user, *pass;
1440         
1441         /* see if we can load it from the cache */      
1442         if (current_newsgroup_name != NULL && g_ascii_strcasecmp (newsgroup_name, current_newsgroup_name) == 0) {
1443                 *result_file_list = current_newsgroup_files;
1444                 return GNOME_VFS_OK;
1445         }
1446         
1447         *result_file_list = NULL;
1448         
1449         /* we don't have it in the cache, so load it from the server */ 
1450         if (current_newsgroup_name != NULL) {
1451                 free_nntp_file_list (current_newsgroup_files);
1452                 g_free (current_newsgroup_name);
1453                 current_newsgroup_name = NULL;
1454         }
1455                 
1456         group_command = g_strdup_printf ("GROUP %s", newsgroup_name);
1457         
1458         result = do_basic_command (conn, group_command);
1459         g_free (group_command);
1460
1461         if (result != GNOME_VFS_OK || conn->response_code != 211) {
1462                 /* if we're anonymous, prompt for a password and try that */
1463                 if (conn->anonymous) {
1464                         get_auth_info(conn, &user, &pass);
1465                         
1466                         if (user != NULL) {
1467                                 conn->anonymous = FALSE;                                
1468                                 tmpstring = g_strdup_printf ("AUTHINFO user %s", user);
1469                                 result = do_basic_command (conn, tmpstring);
1470                                 g_free (tmpstring);
1471
1472                                 if (IS_300 (conn->response_code)) {
1473                                         tmpstring = g_strdup_printf ("AUTHINFO pass %s", pass);
1474                                         result = do_basic_command (conn, tmpstring);
1475                                         g_free (tmpstring);
1476                         
1477                                         group_command = g_strdup_printf ("GROUP %s", newsgroup_name);
1478                                         result = do_basic_command (conn, group_command);
1479                                         g_free (group_command);         
1480
1481                                 }
1482                         }
1483                         g_free (user);
1484                         g_free (pass);
1485                 }
1486                 
1487                 if (result != GNOME_VFS_OK || conn->response_code != 211) {
1488                         g_message ("couldnt set group to %s, code %d", newsgroup_name, conn->response_code);
1489                                 return GNOME_VFS_ERROR_NOT_FOUND; /* could differentiate error better */
1490                 }
1491         }
1492         
1493         sscanf (conn->response_message, "%d %d %d", &total_messages, &first_message, &last_message);
1494
1495         /* read in the header and build the file list for the connection */
1496         if ((last_message - first_message) > MAX_MESSAGE_COUNT) {
1497                 first_message = last_message - MAX_MESSAGE_COUNT;
1498         }
1499         
1500         command_str = g_strdup_printf ("XOVER %d-%d", first_message, last_message);
1501         file_list = assemble_files_from_overview (conn, command_str);
1502         g_free (command_str);
1503         
1504         current_newsgroup_name = g_strdup (newsgroup_name);
1505         current_newsgroup_files = file_list;
1506         
1507         *result_file_list = file_list;
1508         return GNOME_VFS_OK;
1509 }
1510
1511 /* utility to strip leading and trailing slashes from a string. It frees the input string and
1512  * returns a new one
1513  */
1514 static char*
1515 strip_slashes (char* source_string)
1516 {
1517         char *temp_str, *result_str;
1518         int last_offset;
1519         
1520         temp_str = source_string;
1521         if (temp_str[0] == '/') {
1522                 temp_str += 1;  
1523         }       
1524         last_offset = strlen(temp_str) - 1;
1525         if (temp_str[last_offset] == '/') {
1526                 temp_str[last_offset] = '\0';           
1527         }
1528
1529         result_str = g_strdup(temp_str);
1530         g_free(source_string);
1531         return result_str;
1532 }
1533
1534 /* parse the uri to extract the newsgroup name and the file name */
1535 static void
1536 extract_newsgroup_and_filename(GnomeVFSURI *uri, char** newsgroup, char **directory, char **filename)
1537 {
1538         char *dirname, *slash_pos;
1539         
1540         *filename = gnome_vfs_unescape_string(gnome_vfs_uri_extract_short_name (uri), "");
1541         *directory = NULL;
1542          
1543         dirname = strip_slashes(gnome_vfs_uri_extract_dirname(uri));
1544         
1545         *newsgroup = gnome_vfs_unescape_string (dirname, "");
1546         slash_pos = strchr (*newsgroup, '/');
1547         if (slash_pos != NULL) {
1548                 *slash_pos = '\0';
1549                 *directory = g_strdup (slash_pos + 1);
1550         }
1551         g_free (dirname);
1552 }
1553
1554 /* fetch the nntp_file object from the passed in uri */
1555 static nntp_file*
1556 nntp_file_from_uri (NNTPConnection *conn, GnomeVFSURI *uri)
1557 {
1558         GnomeVFSResult result;
1559         char *newsgroup_name, *file_name, *directory_name;
1560         nntp_file *file;
1561         GList *file_list;
1562         
1563         /* extract the newsgroup name */
1564         extract_newsgroup_and_filename(uri, &newsgroup_name, &directory_name, &file_name);
1565         
1566         /* load the (cached) file list */
1567         result = get_files_from_newsgroup (conn, newsgroup_name, &file_list);   
1568         if (file_list == NULL) {
1569                 file = NULL;
1570         } else {
1571                 /* parse the uri into the newsgroup and file */
1572                 if (directory_name != NULL) {
1573                         file = look_up_file (file_list, directory_name, TRUE);
1574                         if (file != NULL) {
1575                                 file = look_up_file (file->part_list, file_name, FALSE);
1576                         }
1577                 } else {
1578                         file = look_up_file (file_list, file_name, FALSE);      
1579                 }
1580         }
1581         
1582         g_free(newsgroup_name);
1583         g_free(file_name);
1584         g_free(directory_name);
1585         
1586         return file;
1587 }
1588
1589 /* determine if a file has all of it's fragments */
1590 static gboolean
1591 has_all_fragments (nntp_file *file)
1592 {
1593         return g_list_length (file->part_list) >= file->total_parts;
1594 }
1595
1596 /* add a file fragment to the file list, creating a new file object if necessary */
1597 static GList *add_file_fragment(GList* file_list, char* filename, char* folder_name, char* fragment_id, int fragment_size,
1598                                 int part_number, int part_total, time_t mod_date)
1599 {
1600         nntp_file *base_file;
1601
1602         /* don't use part 0 */
1603         if (part_number == 0) {
1604                 return file_list;
1605         }
1606         
1607         /* get the file object associated with the filename if any */
1608         base_file = look_up_file(file_list, filename, FALSE);
1609         if (base_file == NULL) {
1610                 base_file = nntp_file_new(filename, folder_name, part_total);
1611                 base_file->mod_date = mod_date;
1612                 file_list = g_list_append(file_list, base_file);
1613         }
1614         
1615         /* add the fragment to the file */
1616         nntp_file_add_part(base_file, part_number, fragment_id, fragment_size);
1617         return file_list;
1618 }
1619
1620 /* remove any partial files from the file list */
1621 static GList *remove_partial_files (GList *file_list)
1622 {
1623         nntp_file* file;
1624         
1625         GList* next_file;
1626         GList* current_file = file_list;
1627
1628         while (current_file != NULL) {
1629                 next_file = current_file->next;
1630                 file = (nntp_file*) current_file->data;
1631                 if (!has_all_fragments (file)) {
1632                         file_list = g_list_remove_link (file_list, current_file);
1633                         nntp_file_destroy (file);
1634                 }
1635                 current_file = next_file;
1636         }       
1637         return file_list;
1638 }
1639
1640
1641 /* loop through the file list to update the size fields */
1642 static void
1643 update_file_sizes(GList *file_list)
1644 {
1645         nntp_file* file;        
1646         GList* current_file = file_list;
1647                 
1648         while (current_file != NULL) {
1649                 file = (nntp_file*) current_file->data;
1650                 file->file_size = nntp_file_get_total_size(file);
1651                 current_file = current_file->next;
1652         }       
1653 }
1654
1655 /* the following set of routines are used to group the files into psuedo-directories by using
1656  * a hash table containing lists of nntp_file objects.
1657  */
1658
1659 /* add the passed in file's foldername to the hash table */
1660 static void
1661 add_file_to_folder (GHashTable *folders, nntp_file *file)
1662 {
1663         GList *folder_contents;
1664         
1665         if (file->folder_name == NULL) {
1666                 return;
1667         }
1668         
1669         folder_contents = g_hash_table_lookup (folders, file->folder_name);
1670         if (folder_contents != NULL) {
1671                 g_list_append(folder_contents, file);
1672         } else {
1673                 folder_contents = g_list_append(NULL, file);
1674                 g_hash_table_insert (folders, g_strdup(file->folder_name), folder_contents);
1675         }
1676 }
1677
1678 /* remove non-singleton files that are contained in folders from the file list */
1679 static void
1680 remove_file_from_list (gpointer key, gpointer value, gpointer callback_data)
1681 {
1682         GList *element_list;
1683         nntp_file* file;
1684         GList** file_list_ptr;
1685                 
1686         file_list_ptr = (GList**) callback_data;
1687         element_list = (GList*) value;
1688
1689         /* if there is more than one element in the list, remove all of them from the file list */      
1690         if (element_list != NULL && g_list_length(element_list) > 1) {
1691                 while (element_list != NULL) {
1692                         file = (nntp_file*) element_list->data;
1693                         *file_list_ptr = g_list_remove(*file_list_ptr, element_list->data);
1694                         element_list = element_list->next;
1695                 }
1696         }       
1697 }
1698
1699 static void
1700 remove_contained_files(GHashTable *folders, GList** file_list_ptr)
1701 {
1702         /* iterate through the passed in hash table to find the files to remove */
1703         g_hash_table_foreach (folders, remove_file_from_list, file_list_ptr);   
1704 }
1705
1706 /* utility routine to calculate a folder's mod-date by inspecting the dates of it's constituents */
1707 static void
1708 calculate_folder_mod_date (nntp_file *folder)
1709 {
1710         time_t latest_mod_date;
1711         GList *current_file;
1712         nntp_file* current_file_data;
1713         
1714         latest_mod_date = 0;
1715         current_file = folder->part_list;
1716         while (current_file != NULL) {
1717                 current_file_data = (nntp_file*) current_file->data;
1718                 if (current_file_data->mod_date > latest_mod_date) {
1719                         latest_mod_date = current_file_data->mod_date;
1720                 }
1721                 current_file = current_file->next;
1722         }
1723         folder->mod_date = latest_mod_date;
1724 }
1725
1726 /* generate folder objects from the entries in the hash table */
1727 static void
1728 generate_folder_from_element (gpointer key, gpointer value, gpointer callback_data)
1729 {
1730         GList *element_list;
1731         int number_of_elements;
1732         nntp_file *new_folder;
1733         char* key_as_string;
1734         GList** file_list_ptr;
1735                 
1736         file_list_ptr = (GList**) callback_data;
1737         element_list = (GList*) value;
1738         key_as_string = (char*) key;
1739         
1740         number_of_elements = g_list_length(element_list);
1741         if (number_of_elements > 1) {
1742                 if (strlen(key_as_string) == 0) {
1743                         key_as_string = "Unknown Title";
1744                 }
1745                 
1746                 new_folder = nntp_file_new (key_as_string, NULL, number_of_elements);
1747                 new_folder->is_directory = TRUE;
1748                 new_folder->file_type = g_strdup ("x-directory/normal");
1749                 new_folder->part_list = g_list_copy (element_list);
1750                 
1751                 calculate_folder_mod_date (new_folder);
1752                 
1753                 *file_list_ptr = g_list_append (*file_list_ptr, new_folder);
1754         }
1755 }
1756
1757 static void
1758 generate_folders(GHashTable *folders, GList** file_list_ptr)
1759 {
1760         /* iterate through the passed in hash table to generate a folder for each non-singleton entry */
1761         g_hash_table_foreach (folders, generate_folder_from_element, file_list_ptr);
1762 }
1763
1764 /* helper routine to deallocate hash table elements */
1765 static gboolean
1766 destroy_element (gpointer key, gpointer value, gpointer data)
1767 {
1768         g_free(key);
1769         g_list_free((GList*) value);
1770         return TRUE;
1771 }
1772
1773 /* deallocate the data structures associate with the folder hash table */
1774 static void
1775 destroy_folder_hash(GHashTable *folders)
1776 {
1777         /* iterate through the hash table to destroy the lists it contains, before destroying
1778          *the table itself
1779          */
1780         g_hash_table_foreach_remove (folders, destroy_element, NULL);
1781         g_hash_table_destroy(folders);
1782 }
1783
1784 /* walk through the file list and try to generate folders to group files with similar subjects */
1785 static GList*
1786 assemble_folders (GList *file_list)
1787 {
1788         GList *current_item;
1789         nntp_file *current_file;
1790         GHashTable *folders;
1791         GList* file_list_ptr;
1792
1793         /* need indirection for file list head */
1794         file_list_ptr = file_list;
1795         
1796         /* make a pass through the files grouping the ones with matching foldernames.  Use a hash
1797          * table for fast lookups
1798          */
1799         folders = g_hash_table_new (g_str_hash, g_str_equal);
1800         current_item = file_list_ptr;
1801         
1802         while (current_item != NULL) {
1803                 current_file = (nntp_file*) current_item->data;
1804                 if (current_file->folder_name != NULL) {
1805                         add_file_to_folder (folders, current_file);     
1806                 }
1807                 current_item = current_item->next;      
1808         }
1809
1810         /* make a pass through the folder list to remove contained files from the file list */
1811         remove_contained_files (folders, &file_list_ptr);
1812         
1813         /* generate folders from the hash table (not singletons) */
1814         generate_folders (folders, &file_list_ptr);
1815         
1816         /* deallocate resources now that we're done */
1817         destroy_folder_hash (folders);
1818
1819         return file_list_ptr;
1820 }
1821
1822 /* this is the main loop for assembling the files.  It issues the passed in
1823  * overview command and then reads the headers one at a time, processing the
1824  * fragments into a list of files
1825  */
1826 static GList* 
1827 assemble_files_from_overview (NNTPConnection *conn, char *command) 
1828 {
1829         GnomeVFSResult result;
1830         char *line = NULL;
1831         int message_size;
1832         int part_number, total_parts;
1833         char* filename, *folder_name, *message_id;
1834         GList *file_list;
1835         time_t mod_date;
1836         
1837         file_list = NULL;
1838         
1839         /* issue the overview command to the server */
1840         result = do_control_write (conn, command);      
1841         
1842         if (result != GNOME_VFS_OK) {
1843                 return file_list;
1844         }
1845         
1846         /* read the response command, and check that it's the right number (eventually) */
1847         result = read_response_line (conn, &line);      
1848         g_free(line);
1849
1850         if (result != GNOME_VFS_OK) {
1851                 return file_list;
1852         }
1853
1854         while (TRUE) {
1855                 result = read_response_line (conn, &line);      
1856                 if (line[0] != '.' && line[1] != '\r') {
1857                         if (parse_header (line, &filename, &folder_name, &message_id, &message_size, &part_number, &total_parts, &mod_date)) {
1858                                 file_list = add_file_fragment (file_list, filename, folder_name, message_id, message_size,
1859                                                 part_number, total_parts, mod_date);
1860                                 g_free (filename);
1861                                 g_free (folder_name);
1862                                 g_free (message_id);
1863                         }               
1864                 } else {
1865                         break;
1866                 }
1867                 g_free(line);
1868         }
1869         
1870         file_list = remove_partial_files (file_list);
1871         update_file_sizes (file_list);
1872         file_list = assemble_folders (file_list);
1873         
1874         return file_list;
1875 }
1876
1877 /* newsgroup files aren't local */
1878 gboolean 
1879 do_is_local (GnomeVFSMethod *method, 
1880              const GnomeVFSURI *uri)
1881 {
1882         return FALSE;
1883 }
1884
1885 static void
1886 prepare_to_read_file (NNTPConnection *conn, nntp_file *file)
1887 {
1888         conn->current_file = file;
1889         conn->current_fragment = NULL;
1890         conn->buffer_offset = 0;
1891         conn->amount_in_buffer = 0;
1892         conn->eof_flag = FALSE;
1893         conn->uu_decode_mode = FALSE;
1894         conn->base_64_decode_mode = FALSE;
1895         
1896         nntp_connection_reset_buffer (conn);
1897 }
1898
1899 static GnomeVFSResult 
1900 do_open (GnomeVFSMethod *method,
1901          GnomeVFSMethodHandle **method_handle,
1902          GnomeVFSURI *uri,
1903          GnomeVFSOpenMode mode,
1904          GnomeVFSContext *context) 
1905 {
1906         GnomeVFSResult result;
1907         NNTPConnection *conn;
1908         nntp_file *file;
1909         const char* basename;
1910
1911         /* we can save allocating a connection to search for the .directory file */
1912         basename = gnome_vfs_uri_extract_short_name(uri);
1913         if (strcmp(basename, ".directory") == 0) {
1914                 return GNOME_VFS_ERROR_NOT_FOUND;
1915         }
1916                 
1917         result = nntp_connection_acquire (uri, &conn, context);
1918         if (result != GNOME_VFS_OK) 
1919                 return result;
1920                 
1921         /* make sure we have the file */
1922         file = nntp_file_from_uri(conn, uri);
1923         if (file == NULL) {             
1924                 nntp_connection_release (conn);
1925                 return GNOME_VFS_ERROR_NOT_FOUND;
1926         } else {
1927                 prepare_to_read_file (conn, file);              
1928         }
1929         
1930         if (result == GNOME_VFS_OK) {
1931                 *method_handle = (GnomeVFSMethodHandle *) conn;
1932         } else {
1933                 *method_handle = NULL;
1934                 nntp_connection_release (conn);
1935         }
1936         return result;
1937 }
1938
1939 static GnomeVFSResult 
1940 do_close (GnomeVFSMethod *method,
1941           GnomeVFSMethodHandle *method_handle,
1942           GnomeVFSContext *context) 
1943 {
1944         NNTPConnection *conn = (NNTPConnection *) method_handle;
1945
1946         nntp_connection_release (conn);
1947
1948         return GNOME_VFS_OK;
1949 }
1950
1951 static GnomeVFSResult 
1952 do_read (GnomeVFSMethod *method, 
1953          GnomeVFSMethodHandle *method_handle, 
1954          gpointer buffer,
1955          GnomeVFSFileSize num_bytes, 
1956          GnomeVFSFileSize *bytes_read, 
1957          GnomeVFSContext *context) 
1958 {
1959         NNTPConnection *conn = (NNTPConnection * )method_handle;        
1960         return nntp_file_read(conn, buffer, num_bytes, bytes_read);
1961 }
1962
1963 static GnomeVFSResult
1964 do_get_file_info (GnomeVFSMethod *method,
1965                   GnomeVFSURI *uri,
1966                   GnomeVFSFileInfo *file_info,
1967                   GnomeVFSFileInfoOptions options,
1968                   GnomeVFSContext *context) 
1969 {
1970         const char* host_name;
1971         const char* temp_str, *first_slash;
1972         
1973         GnomeVFSURI *parent = gnome_vfs_uri_get_parent (uri);
1974         GnomeVFSResult result;
1975
1976         host_name = gnome_vfs_uri_get_host_name(uri);
1977         if (host_name == NULL) {
1978                 return GNOME_VFS_ERROR_INVALID_HOST_NAME;
1979         }
1980
1981         /* if it's the top level newsgroup, treat it like a directory */        
1982         temp_str = gnome_vfs_uri_get_path(uri);
1983         first_slash = strchr(temp_str + 1, '/');        
1984         if (parent == NULL || first_slash == NULL) {
1985                 /* this is a request for info about the root directory */
1986                 file_info->name = g_strdup("/");
1987                 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
1988                 file_info->mime_type = g_strdup ("x-directory/normal");
1989                 file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ |
1990                                                 GNOME_VFS_PERM_OTHER_READ; 
1991                 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE |
1992                         GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE |
1993                         GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS;
1994                 
1995                 return GNOME_VFS_OK;
1996         } else {
1997                 GnomeVFSMethodHandle *method_handle;
1998                 char *name;
1999                 
2000                 result = do_open_directory (method, &method_handle, parent,
2001                                             options, context);
2002                 gnome_vfs_uri_unref (parent);
2003
2004                 if (result != GNOME_VFS_OK) {
2005                         return result;
2006                 }
2007
2008                 name = gnome_vfs_uri_extract_short_name (uri);          
2009                 while (result == GNOME_VFS_OK) {
2010                         result = do_read_directory (method, method_handle, 
2011                                                     file_info, context);
2012                         if (result == GNOME_VFS_OK) {
2013                                 if (file_info->name && !strcmp (file_info->name,
2014                                                                 name)) {
2015                                         g_free (name);
2016                                         do_close_directory(
2017                                                         method, method_handle,
2018                                                         context);
2019                                         return GNOME_VFS_OK;
2020                                 }
2021
2022                                 gnome_vfs_file_info_clear (file_info);
2023                         }
2024                 }
2025                 do_close_directory(method, method_handle, context);
2026         }
2027
2028         return GNOME_VFS_ERROR_NOT_FOUND;
2029 }
2030
2031 static GnomeVFSResult
2032 do_get_file_info_from_handle (GnomeVFSMethod *method,
2033                               GnomeVFSMethodHandle *method_handle,
2034                               GnomeVFSFileInfo *file_info,
2035                               GnomeVFSFileInfoOptions options,
2036                               GnomeVFSContext *context)
2037 {
2038         return do_get_file_info (method,
2039                                  ((NNTPConnection *)method_handle)->uri, 
2040                                  file_info, options, context);
2041 }
2042
2043 static GnomeVFSResult
2044 do_open_directory (GnomeVFSMethod *method,
2045                    GnomeVFSMethodHandle **method_handle,
2046                    GnomeVFSURI *uri,
2047                    GnomeVFSFileInfoOptions options,
2048                    GnomeVFSContext *context)
2049 {
2050         const char *newsgroup_server;
2051         char *newsgroup_name;
2052         char *directory_name, *mapped_dirname;
2053         
2054         NNTPConnection *conn;
2055         GnomeVFSResult result;
2056         nntp_file *file;
2057         GList *file_list;
2058         
2059         newsgroup_server = gnome_vfs_uri_get_host_name (uri);
2060         newsgroup_name = gnome_vfs_uri_extract_dirname (uri);
2061         directory_name = g_strdup (gnome_vfs_uri_extract_short_name (uri));
2062         
2063         if (strcmp (newsgroup_name, "/") == 0 || strlen(newsgroup_name) == 0) {
2064                 g_free (newsgroup_name);
2065                 newsgroup_name = directory_name;
2066                 directory_name = NULL;
2067         }
2068
2069         if (newsgroup_name == NULL) {
2070                 g_free(directory_name);
2071                 return GNOME_VFS_ERROR_NOT_FOUND;
2072         }
2073
2074         newsgroup_name = strip_slashes (newsgroup_name);
2075         result = nntp_connection_acquire (uri, &conn, context);
2076         if (result != GNOME_VFS_OK) {
2077                 g_free (newsgroup_name);
2078                 g_free (directory_name);
2079                 return result;
2080         }
2081
2082         /* get the files from the newsgroup */
2083         result = get_files_from_newsgroup (conn, newsgroup_name, &file_list);
2084         if (result != GNOME_VFS_OK) {
2085                 g_free (newsgroup_name);
2086                 g_free (directory_name);
2087                 nntp_connection_release(conn);
2088                 return result;
2089         }
2090                 
2091         if (directory_name == NULL) {
2092                 conn->next_file = file_list;
2093         } else {
2094                 if (file_list == NULL) {
2095                         file = NULL;
2096                 } else {
2097                         mapped_dirname = gnome_vfs_unescape_string (directory_name, "");
2098                         file = look_up_file (file_list, mapped_dirname, TRUE);
2099                         g_free (mapped_dirname);
2100                 }
2101                 
2102                 if (file == NULL) {
2103                         g_message ("couldnt find file %s", directory_name);
2104                         return GNOME_VFS_ERROR_NOT_FOUND;
2105                 }
2106                 if (file->is_directory) {
2107                         conn->next_file = file->part_list;
2108                 } else {
2109                         conn->next_file = NULL;
2110                 }
2111         }
2112                 
2113         if (result != GNOME_VFS_OK) {
2114                 g_message ("couldnt set group!");
2115                 nntp_connection_release (conn);
2116                 
2117                 g_free (newsgroup_name);
2118                 g_free (directory_name);
2119                 return result;
2120         }
2121         *method_handle = (GnomeVFSMethodHandle *) conn;
2122
2123         g_free (newsgroup_name);
2124         g_free (directory_name);
2125
2126         return result;
2127 }
2128
2129 static GnomeVFSResult
2130 do_close_directory (GnomeVFSMethod *method,
2131                     GnomeVFSMethodHandle *method_handle,
2132                     GnomeVFSContext *context) 
2133 {
2134         NNTPConnection *conn = (NNTPConnection *) method_handle;
2135
2136         nntp_connection_release (conn);
2137
2138         return GNOME_VFS_OK;
2139 }
2140
2141
2142 static GnomeVFSResult
2143 do_read_directory (GnomeVFSMethod *method,
2144                    GnomeVFSMethodHandle *method_handle,
2145                    GnomeVFSFileInfo *file_info,
2146                    GnomeVFSContext *context)
2147 {
2148         NNTPConnection *conn = (NNTPConnection *) method_handle;
2149         nntp_file *file_data;
2150         const char* mime_string;
2151         
2152         if (!conn->next_file)
2153                 return GNOME_VFS_ERROR_EOF;
2154         
2155         /* fill out the information for the current file, and bump the pointer */
2156         gnome_vfs_file_info_clear(file_info);
2157         
2158         /* implement the size threshold by skipping files smaller than the threshold */
2159         file_data = (nntp_file*) conn->next_file->data;
2160         while (file_data->file_size < MIN_FILE_SIZE_THRESHOLD && !file_data->is_directory) {
2161                 conn->next_file = conn->next_file->next;
2162                 if (conn->next_file == NULL) {
2163                         return GNOME_VFS_ERROR_EOF;
2164                 } else {
2165                         file_data = (nntp_file*) conn->next_file->data;
2166                 }
2167         }
2168         
2169         file_info->name = g_strdup(file_data->file_name);
2170         
2171         file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ |
2172                                  GNOME_VFS_PERM_USER_WRITE |GNOME_VFS_PERM_OTHER_READ;
2173                                  
2174         file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE |
2175                         GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE |
2176                         GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS |
2177                         GNOME_VFS_FILE_INFO_FIELDS_MTIME;
2178         
2179         /* handle the case when it's a directory */
2180         if (file_data->is_directory) {
2181                 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
2182                 file_info->mime_type = g_strdup ("x-directory/normal");
2183                 file_info->mtime = file_data->mod_date;
2184                 file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ |
2185                                                 GNOME_VFS_PERM_USER_WRITE |
2186                                                 GNOME_VFS_PERM_OTHER_READ | GNOME_VFS_PERM_USER_EXEC |
2187                                                 GNOME_VFS_PERM_GROUP_EXEC | GNOME_VFS_PERM_OTHER_EXEC;                  
2188         } else {
2189         
2190                 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
2191
2192                 file_info->mtime = file_data->mod_date;
2193                 
2194                 /* figure out the mime type from the extension; if it's unrecognized, use "text/plain"
2195                 * instead of "application/octet-stream"
2196                 */
2197                 mime_string = gnome_vfs_mime_type_from_name(file_data->file_name);
2198                 if (strcmp(mime_string, "application/octet-stream") == 0) {
2199                         file_info->mime_type = g_strdup("text/plain");  
2200                 } else {
2201                         file_info->mime_type = g_strdup(mime_string);   
2202                 }
2203         
2204                 file_info->size = file_data->file_size; 
2205                 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SIZE;
2206         }
2207         
2208         conn->next_file = conn->next_file->next;
2209         return GNOME_VFS_OK;
2210 }
2211
2212 static GnomeVFSResult
2213 do_check_same_fs (GnomeVFSMethod *method,
2214       GnomeVFSURI *a,
2215       GnomeVFSURI *b,
2216       gboolean *same_fs_return,
2217       GnomeVFSContext *context)
2218 {
2219         *same_fs_return = nntp_connection_uri_equal (a,b);
2220         return GNOME_VFS_OK;
2221 }
2222
2223 static GnomeVFSMethod method = {
2224         sizeof (GnomeVFSMethod),
2225         do_open,
2226         NULL, /* create */
2227         do_close,
2228         do_read,
2229         NULL, /* write */
2230         NULL, /* seek */
2231         NULL, /* tell */
2232         NULL, /* truncate */
2233         do_open_directory,
2234         do_close_directory,
2235         do_read_directory,
2236         do_get_file_info,
2237         do_get_file_info_from_handle,
2238         do_is_local,
2239         NULL, /* make_directory */
2240         NULL, /* remove directory */
2241         NULL, /* move */
2242         NULL, /* unlink */
2243         do_check_same_fs,
2244         NULL, /* set_file_info */
2245         NULL, /* truncate */
2246         NULL, /* find_directory */
2247         NULL  /* create_symbolic_link */
2248 };
2249
2250 GnomeVFSMethod *
2251 vfs_module_init (const char *method_name, 
2252                  const char *args)
2253 {
2254 #ifdef HAVE_GCONF
2255         char *argv[] = {"vfs-nntp-method"};
2256         int argc = 1;
2257
2258         /* Ensure GConf is initialized.  If more modules start to rely on
2259          * GConf, then this should probably be moved into a more 
2260          * central location
2261          */
2262
2263         if (!gconf_is_initialized ()) {
2264                 /* auto-initializes OAF if necessary */
2265                 gconf_init (argc, argv, NULL);
2266         }
2267 #endif
2268         
2269         return &method;
2270 }
2271
2272 void
2273 vfs_module_shutdown (GnomeVFSMethod *method)
2274 {
2275 }