/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */ /* nntp-method.c - VFS module for NNTP Copyright (C) 2001 Andy Hertzfeld The Gnome Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The Gnome Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the Gnome Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. based on Ian McKellar's (yakk@yakk.net) ftp method for gnome-vfs presents a high level, file-oriented view of a newsgroup, integrating file fragments and organizing them in folders Author: Andy Hertzfeld December 2001 */ #ifdef HAVE_CONFIG_H #include #endif #include /* for atoi */ #include /* for sscanf */ #include #include #include #include #include #ifdef HAVE_GCONF #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "nntp-method.h" #define MAX_RESPONSE_SIZE 4096 #define READ_BUFFER_SIZE 16384 /* these parameters should eventually be fetched from gconf */ #define MAX_MESSAGE_COUNT 2400 #define MIN_FILE_SIZE_THRESHOLD 4095 /* macros for the checking of NNTP response codes */ #define IS_100(X) ((X) >= 100 && (X) < 200) #define IS_200(X) ((X) >= 200 && (X) < 300) #define IS_300(X) ((X) >= 300 && (X) < 400) #define IS_400(X) ((X) >= 400 && (X) < 500) #define IS_500(X) ((X) >= 500 && (X) < 600) static GnomeVFSResult do_open (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSOpenMode mode, GnomeVFSContext *context); static gboolean do_is_local (GnomeVFSMethod *method, const GnomeVFSURI *uri); static GnomeVFSResult do_open_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSFileInfoOptions options, GnomeVFSContext *context); static GnomeVFSResult do_close_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSContext *context); static GnomeVFSResult do_read_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileInfo *file_info, GnomeVFSContext *context); guint nntp_connection_uri_hash (gconstpointer c); int nntp_connection_uri_equal (gconstpointer c, gconstpointer d); static GnomeVFSResult nntp_connection_acquire (GnomeVFSURI *uri, NNTPConnection **connection, GnomeVFSContext *context); static void nntp_connection_release (NNTPConnection *conn); static GList* assemble_files_from_overview (NNTPConnection *conn, char *command); static const char *anon_user = "anonymous"; static const char *anon_pass = "nobody@gnome.org"; static const int nntp_port = 119; /* A GHashTable of GLists of NNTPConnections */ static GHashTable *spare_connections = NULL; G_LOCK_DEFINE_STATIC (spare_connections); static int total_connections = 0; static int allocated_connections = 0; /* single element cache of file objects for a given newsgroup */ static char* current_newsgroup_name = NULL; static GList* current_newsgroup_files = NULL; static GnomeVFSResult nntp_response_to_vfs_result (NNTPConnection *conn) { int response = conn->response_code; switch (response) { case 421: case 426: return GNOME_VFS_ERROR_CANCELLED; case 425: return GNOME_VFS_ERROR_ACCESS_DENIED; case 530: case 331: case 332: case 532: return GNOME_VFS_ERROR_LOGIN_FAILED; case 450: case 451: case 550: case 551: return GNOME_VFS_ERROR_NOT_FOUND; case 452: case 552: return GNOME_VFS_ERROR_NO_SPACE; case 553: return GNOME_VFS_ERROR_BAD_FILE; } /* is this the correct interpretation of this error? */ if (IS_100 (response)) return GNOME_VFS_OK; if (IS_200 (response)) return GNOME_VFS_OK; /* is this the correct interpretation of this error? */ if (IS_300 (response)) return GNOME_VFS_OK; if (IS_400 (response)) return GNOME_VFS_ERROR_GENERIC; if (IS_500 (response)) return GNOME_VFS_ERROR_INTERNAL; return GNOME_VFS_ERROR_GENERIC; } static GnomeVFSResult read_response_line(NNTPConnection *conn, char **line) { GnomeVFSFileSize bytes = MAX_RESPONSE_SIZE, bytes_read; char *ptr, *buf = g_malloc (MAX_RESPONSE_SIZE+1); int line_length; GnomeVFSResult result = GNOME_VFS_OK; while (!strstr (conn->response_buffer->str, "\r\n")) { /* we don't have a full line. Lets read some... */ bytes_read = 0; result = gnome_vfs_socket_buffer_read (conn->socketbuf, buf, bytes, &bytes_read); buf[bytes_read] = '\0'; conn->response_buffer = g_string_append (conn->response_buffer, buf); if (result != GNOME_VFS_OK) { g_warning ("Error `%s' during read\n", gnome_vfs_result_to_string(result)); g_free (buf); return result; } } g_free (buf); ptr = strstr (conn->response_buffer->str, "\r\n"); line_length = ptr - conn->response_buffer->str; *line = g_strndup (conn->response_buffer->str, line_length); g_string_erase (conn->response_buffer, 0 , line_length + 2); return result; } static GnomeVFSResult get_response (NNTPConnection *conn) { /* all that should be pending is a response to the last command */ GnomeVFSResult result; while (TRUE) { char *line = NULL; result = read_response_line (conn, &line); if (result != GNOME_VFS_OK) { g_free (line); g_warning ("Error reading response line."); return result; } /* response needs to be at least: "### x" - I think*/ if (g_ascii_isdigit (line[0]) && g_ascii_isdigit (line[1]) && g_ascii_isdigit (line[2]) && g_ascii_isspace (line[3])) { conn->response_code = g_ascii_digit_value (line[0]) * 100 + g_ascii_digit_value (line[1]) * 10 + g_ascii_digit_value (line[2]); if (conn->response_message) g_free (conn->response_message); conn->response_message = g_strdup (line+4); g_free (line); return nntp_response_to_vfs_result (conn); } /* hmm - not a valid line - lets ignore it :-) */ g_free (line); } return GNOME_VFS_OK; /* should never be reached */ } static GnomeVFSResult do_control_write (NNTPConnection *conn, char *command) { char *actual_command = g_strdup_printf ("%s\r\n", command); GnomeVFSFileSize bytes = strlen (actual_command), bytes_written; GnomeVFSResult result = gnome_vfs_socket_buffer_write (conn->socketbuf, actual_command, bytes, &bytes_written); gnome_vfs_socket_buffer_flush (conn->socketbuf); g_free (actual_command); return result; } static void nntp_connection_reset_buffer (NNTPConnection *conn) { g_string_erase (conn->response_buffer, 0, strlen(conn->response_buffer->str)); } static GnomeVFSResult do_basic_command (NNTPConnection *conn, char *command) { GnomeVFSResult result; nntp_connection_reset_buffer (conn); result = do_control_write(conn, command); if (result != GNOME_VFS_OK) { return result; } result = get_response (conn); return result; } /* the following routines manage the connection with the news server */ /* create a new connection */ static GnomeVFSResult nntp_connection_create (NNTPConnection **connptr, GnomeVFSURI *uri, GnomeVFSContext *context) { NNTPConnection *conn = g_new (NNTPConnection, 1); GnomeVFSResult result; char *tmpstring; int port = nntp_port; const char *user = anon_user; const char *pass = anon_pass; conn->uri = gnome_vfs_uri_dup (uri); conn->response_buffer = g_string_new (""); conn->response_message = NULL; conn->response_code = -1; conn->anonymous = TRUE; /* allocate the read buffer */ conn->buffer = g_malloc(READ_BUFFER_SIZE); conn->buffer_size = READ_BUFFER_SIZE; conn->amount_in_buffer = 0; conn->buffer_offset = 0; conn->request_in_progress = FALSE; if (gnome_vfs_uri_get_host_port (uri)) { port = gnome_vfs_uri_get_host_port (uri); } if (gnome_vfs_uri_get_user_name (uri)) { user = gnome_vfs_uri_get_user_name (uri); conn->anonymous = FALSE; } if (gnome_vfs_uri_get_password (uri)) { pass = gnome_vfs_uri_get_password (uri); } result = gnome_vfs_inet_connection_create (&conn->inet_connection, gnome_vfs_uri_get_host_name (uri), port, context ? gnome_vfs_context_get_cancellation(context) : NULL); if (result != GNOME_VFS_OK) { g_warning ("gnome_vfs_inet_connection_create (\"%s\", %d) = \"%s\"", gnome_vfs_uri_get_host_name (uri), gnome_vfs_uri_get_host_port (uri), gnome_vfs_result_to_string (result)); g_string_free (conn->response_buffer, TRUE); g_free (conn); return result; } conn->socketbuf = gnome_vfs_inet_connection_to_socket_buffer (conn->inet_connection); if (conn->socketbuf == NULL) { g_warning ("gnome_vfs_inet_connection_get_socket_buffer () failed"); gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL); g_string_free (conn->response_buffer, TRUE); g_free (conn); return GNOME_VFS_ERROR_GENERIC; } result = get_response (conn); if (result != GNOME_VFS_OK) { g_warning ("nntp server (%s:%d) said `%d %s'", gnome_vfs_uri_get_host_name (uri), gnome_vfs_uri_get_host_port (uri), conn->response_code, conn->response_message); g_string_free (conn->response_buffer, TRUE); g_free (conn); return result; } if (!conn->anonymous) { tmpstring = g_strdup_printf ("AUTHINFO user %s", user); result = do_basic_command (conn, tmpstring); g_free (tmpstring); if (IS_300 (conn->response_code)) { tmpstring = g_strdup_printf ("AUTHINFO pass %s", pass); result = do_basic_command (conn, tmpstring); g_free (tmpstring); } if (result != GNOME_VFS_OK) { /* login failed */ g_warning ("NNTP server said: \"%d %s\"\n", conn->response_code, conn->response_message); gnome_vfs_socket_buffer_destroy (conn->socketbuf, FALSE); gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL); g_free (conn); return result; } } *connptr = conn; total_connections++; /* g_message("successfully logged into server, total connections: %d", total_connections); */ return GNOME_VFS_OK; } /* destroy the connection object and deallocate the associated resources */ static void nntp_connection_destroy (NNTPConnection *conn) { GnomeVFSResult result; if (conn->inet_connection) { result = do_basic_command(conn, "QUIT"); gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL); } if (conn->socketbuf) gnome_vfs_socket_buffer_destroy (conn->socketbuf, FALSE); gnome_vfs_uri_unref (conn->uri); if (conn->response_buffer) g_string_free(conn->response_buffer, TRUE); g_free (conn->response_message); g_free (conn->server_type); g_free (conn->buffer); g_free (conn); total_connections--; } /* hash routines for managing the pool of connections */ /* g_str_hash and g_str_equal seem to fail with null arguments */ static int my_str_hash (const char *c) { if (c) return g_str_hash (c); else return 0; } static int my_str_equal (gconstpointer c, gconstpointer d) { if ((c && !d) || (d &&!c)) return FALSE; if (!c && !d) return TRUE; return g_str_equal (c,d); } /* hash the bits of a GnomeVFSURI that distingush NNTP connections */ guint nntp_connection_uri_hash (gconstpointer c) { GnomeVFSURI *uri = (GnomeVFSURI *) c; return my_str_hash (gnome_vfs_uri_get_host_name (uri)) + my_str_hash (gnome_vfs_uri_get_user_name (uri)) + my_str_hash (gnome_vfs_uri_get_password (uri)) + gnome_vfs_uri_get_host_port (uri); } /* test the equality of the bits of a GnomeVFSURI that distingush NNTP * connections */ int nntp_connection_uri_equal (gconstpointer c, gconstpointer d) { GnomeVFSURI *uri1 = (GnomeVFSURI *)c; GnomeVFSURI *uri2 = (GnomeVFSURI *) d; return my_str_equal (gnome_vfs_uri_get_host_name(uri1), gnome_vfs_uri_get_host_name (uri2)) && my_str_equal (gnome_vfs_uri_get_user_name (uri1), gnome_vfs_uri_get_user_name (uri2)) && my_str_equal (gnome_vfs_uri_get_password (uri1), gnome_vfs_uri_get_password (uri2)) && gnome_vfs_uri_get_host_port (uri1) == gnome_vfs_uri_get_host_port (uri2); } static GnomeVFSResult nntp_connection_acquire (GnomeVFSURI *uri, NNTPConnection **connection, GnomeVFSContext *context) { GList *possible_connections; NNTPConnection *conn = NULL; GnomeVFSResult result = GNOME_VFS_OK; G_LOCK (spare_connections); if (spare_connections == NULL) { spare_connections = g_hash_table_new (nntp_connection_uri_hash, nntp_connection_uri_equal); } possible_connections = g_hash_table_lookup (spare_connections, uri); if (possible_connections) { /* spare connection(s) found */ conn = (NNTPConnection *) possible_connections->data; possible_connections = g_list_remove (possible_connections, conn); if(!g_hash_table_lookup (spare_connections, uri)) { /* uri will be used as a key in the hashtable */ uri = gnome_vfs_uri_dup (uri); } g_hash_table_insert (spare_connections, uri, possible_connections); /* make sure connection hasn't timed out */ result = do_basic_command(conn, "MODE READER"); if (result != GNOME_VFS_OK) { nntp_connection_destroy (conn); result = nntp_connection_create (&conn, uri, context); } } else { result = nntp_connection_create (&conn, uri, context); } G_UNLOCK (spare_connections); *connection = conn; if(result == GNOME_VFS_OK) allocated_connections++; return result; } static void nntp_connection_release (NNTPConnection *conn) { GList *possible_connections; GnomeVFSURI *uri; g_return_if_fail (conn); G_LOCK (spare_connections); if (spare_connections == NULL) spare_connections = g_hash_table_new (nntp_connection_uri_hash, nntp_connection_uri_equal); possible_connections = g_hash_table_lookup (spare_connections, conn->uri); possible_connections = g_list_append (possible_connections, conn); if (g_hash_table_lookup (spare_connections, conn->uri)) { uri = conn->uri; /* no need to duplicate uri */ } else { /* uri will be used as key */ uri = gnome_vfs_uri_dup (conn->uri); } g_hash_table_insert (spare_connections, uri, possible_connections); allocated_connections--; G_UNLOCK(spare_connections); } /* the following routines are the code for assembling the file list from the newsgroup headers. */ /* Here are the folder name filtering routines, which attempt to remove track numbers and other extraneous * info from folder names, to make folder grouping work better. First, there are some character classifying * helper routines */ static gboolean is_number_or_space (char test_char) { return g_ascii_isspace (test_char) || g_ascii_isdigit (test_char) || test_char == '_' || test_char == '-' || test_char == '/'; } static gboolean all_numbers_or_spaces (char *left_ptr, char *right_ptr) { char *current_char_ptr; char current_char; current_char_ptr = left_ptr; while (current_char_ptr < right_ptr) { current_char = *current_char_ptr++; if (!is_number_or_space(current_char)) { return FALSE; } } return TRUE; } /* each of the next set of routines implement a specific filtering operation */ static void remove_numbers_between_dashes(char *input_str) { char *left_ptr, *right_ptr; int length_to_end, segment_size; left_ptr = strchr (input_str, '-'); while (left_ptr != NULL) { right_ptr = strchr(left_ptr + 1, '-'); if (right_ptr != NULL) { segment_size = right_ptr - left_ptr; if (all_numbers_or_spaces(left_ptr, right_ptr) && segment_size > 1) { length_to_end = strlen(right_ptr + 1) + 1; g_memmove(left_ptr, right_ptr + 1, length_to_end); } else { left_ptr = right_ptr; } } else { right_ptr = input_str + strlen(input_str) - 1; if (all_numbers_or_spaces(left_ptr, right_ptr)) { left_ptr = '\0'; } break; } } } static void remove_number_at_end (char *input_str) { char *space_ptr, *next_char; gboolean is_digits; space_ptr = strrchr(input_str, ' '); next_char = space_ptr + 1; if (space_ptr != NULL) { is_digits = TRUE; while (*next_char != '\0') { if (!is_number_or_space (*next_char)) { is_digits = FALSE; break; } next_char += 1; } if (is_digits) { *space_ptr = '\0'; } } } static char* trim_nonalpha_chars (char *input_str) { char *left_ptr, *right_ptr; /* first handle the end of the string */ right_ptr = input_str + strlen(input_str) - 1; while (!g_ascii_isalnum (*right_ptr) && right_ptr > input_str) { right_ptr -= 1; } right_ptr += 1; *right_ptr = '\0'; /* now handle the beginning */ left_ptr = input_str; while (*left_ptr && !g_ascii_isalnum(*left_ptr)) { left_ptr++; } return left_ptr; } static void remove_of_expressions (char *input_str) { char *left_ptr, *right_ptr; int length_to_end; gboolean found_number; left_ptr = strstr (input_str, "of"); if (left_ptr == NULL) { left_ptr = strstr (input_str, "OF"); } if (left_ptr == NULL) { left_ptr = strstr (input_str, "/"); } if (left_ptr != NULL) { found_number = FALSE; right_ptr = left_ptr + 2; left_ptr = left_ptr - 1; while (is_number_or_space(*left_ptr) && left_ptr >= input_str) { found_number = found_number || g_ascii_isdigit(*left_ptr); left_ptr -= 1; } while (is_number_or_space(*right_ptr)) { found_number = found_number || g_ascii_isdigit(*right_ptr); right_ptr += 1; } if (found_number) { length_to_end = strlen(right_ptr); if (length_to_end > 0) { g_memmove(left_ptr + 1, right_ptr, length_to_end + 1); } else { left_ptr += 1; *left_ptr = '\0'; } } } } /* here is the main folder name filtering routine, which returns a new string containing the filtered * version of the passed-in folder name */ static char * filter_folder_name(char *folder_name) { char *temp_str, *save_str, *result_str; char *colon_ptr, *left_ptr, *right_ptr; int length_to_end; temp_str = g_strdup(folder_name); save_str = temp_str; temp_str = g_strstrip(temp_str); /* if there is a colon, strip everything before it */ colon_ptr = strchr(temp_str, ':'); if (colon_ptr != NULL) { temp_str = colon_ptr + 1; } /* remove everything in brackets */ left_ptr = strrchr(temp_str, '['); if (left_ptr != NULL) { right_ptr = strchr(left_ptr, ']'); if (right_ptr != NULL && right_ptr > left_ptr) { length_to_end = strlen(right_ptr + 1) + 1; g_memmove(left_ptr, right_ptr + 1, length_to_end); } } /* eliminate "n of m" expressions */ remove_of_expressions(temp_str); /* remove numbers at end of the string */ remove_number_at_end(temp_str); /* remove numbers between dashes */ remove_numbers_between_dashes(temp_str); /* trim leading and trailing non-alphanumeric characters */ temp_str = trim_nonalpha_chars(temp_str); /* truncate if necessary */ if (strlen(temp_str) > 30) { left_ptr = temp_str + 29; while (g_ascii_isalpha(*left_ptr)) { left_ptr += 1; } *left_ptr = '\0'; } /* return the result */ result_str = g_strdup(temp_str); g_free(save_str); return result_str; } /* parse dates by allowing gnome-vfs to do most of the work */ static void remove_commas(char *source_str) { char *ptr; ptr = source_str; while (*ptr != '\0') { if (*ptr == ',') { g_memmove(ptr, ptr+1, strlen(ptr)); } else { ptr += 1; } } } static gboolean is_number(char *input_str) { char *cur_char; cur_char = input_str; while (*cur_char) { if (!g_ascii_isdigit(*cur_char)) { return FALSE; } cur_char += 1; } return TRUE; } static void parse_date_string (const char* date_string, time_t *t) { struct stat s; char *filename, *linkname; char *temp_str, *mapped_date; gboolean result; char **date_parts; int offset; filename = NULL; linkname = NULL; /* remove commas from day, and flip the day and month */ date_parts = g_strsplit(date_string, " ", 0); if (is_number(date_parts[0])) { offset = 0; } else { offset = 1; remove_commas(date_parts[0]); } temp_str = date_parts[offset]; date_parts[offset] = date_parts[offset + 1]; date_parts[offset + 1] = temp_str; mapped_date = g_strjoinv(" ", date_parts); temp_str = g_strdup_printf("-rw-rw-rw- 1 500 500 %s x", mapped_date); g_strfreev(date_parts); g_free (mapped_date); result = gnome_vfs_parse_ls_lga (temp_str, &s, &filename, &linkname); if (!result) { g_message("error parsing %s, %s", date_string, temp_str); } g_free(temp_str); *t = s.st_mtime; } /* parse_header takes a message header an extracts the subject and id, and then * parses the subject to get the file name and fragment sequence number */ static gboolean parse_header(const char* header_str, char** filename, char** folder_name, char** message_id, int* message_size, int* part_number, int* total_parts, time_t *mod_date) { char* temp_str, *file_start; char* left_paren, *right_paren, *slash_pos; int slash_bump; char **header_parts; gboolean has_count; *part_number = -1; *total_parts = -1; *message_size = 0; *filename = NULL; *folder_name = NULL; *message_id = NULL; slash_pos = NULL; header_parts = g_strsplit(header_str, "\t", 0); temp_str = g_strdup(header_parts[1]); *message_id = g_strdup(header_parts[4]); if (header_parts[6] != NULL) { *message_size = atoi(header_parts[6]); } parse_date_string(header_parts[3], mod_date); g_strfreev(header_parts); /* find the parentheses and extra the part number and total part count */ has_count = FALSE; left_paren = strchr(temp_str,'('); right_paren = strchr(temp_str, ')'); /* if we could't find parentheses, try braces */ if (left_paren == NULL) { left_paren = strchr(temp_str,'['); right_paren = strchr(temp_str, ']'); } while (!has_count && left_paren != NULL && right_paren != NULL) { slash_pos = strchr(left_paren, '/'); slash_bump = 1; if (slash_pos == NULL) { slash_pos = strchr(left_paren, '-'); } if (slash_pos == NULL) { slash_pos = strstr(left_paren, " of "); slash_bump = 4; } if (slash_pos != NULL) { has_count = TRUE; *slash_pos = '\0'; *right_paren = '\0'; *part_number = atoi (left_paren + 1); *total_parts = atoi (slash_pos + slash_bump); } else { left_paren = strchr(right_paren + 1, '('); right_paren = strchr(right_paren + 1, ')'); } } if (!has_count) { *part_number = 1; *total_parts = 1; } /* isolate the filename and copy it to the result */ if (has_count) { *left_paren = '\0'; file_start = strrchr (temp_str, '-'); if (file_start == NULL) { return FALSE; } *filename = g_strdup (g_strstrip (file_start + 1)); *file_start = '\0'; *folder_name = filter_folder_name (temp_str); } else { *filename = g_strdup (temp_str); } g_free(temp_str); return TRUE; } /* create a new file fragment structure */ static nntp_fragment* nntp_fragment_new(int fragment_number, char* fragment_id, int fragment_size) { nntp_fragment* new_fragment = (nntp_fragment*) g_malloc (sizeof (nntp_fragment)); new_fragment->fragment_number = fragment_number; new_fragment->fragment_id = g_strdup (fragment_id); new_fragment->fragment_size = fragment_size; new_fragment->bytes_read = 0; return new_fragment; } /* deallocate the passed in file fragment */ static void nntp_fragment_destroy(nntp_fragment* fragment) { g_free(fragment->fragment_id); g_free(fragment); } /* utility routine to map slashes to dashes in the passed in string so we never return * a filename with slashes */ static char * map_slashes(char *str) { char *current_ptr; current_ptr = str; while (*current_ptr != '\0') { if (*current_ptr == '/') { *current_ptr = '-'; } current_ptr += 1; } return str; } /* allocate a file object */ static nntp_file* nntp_file_new(char* file_name, char *folder_name, int total_parts) { nntp_file* new_file = (nntp_file*) g_malloc (sizeof (nntp_file)); if (strlen(map_slashes(file_name)) < 1) { file_name = "(Empty)"; } new_file->file_name = map_slashes(g_strdup(file_name)); new_file->folder_name = g_strdup(folder_name); new_file->file_type = NULL; new_file->part_list = NULL; new_file->file_size = 0; new_file->total_parts = total_parts; new_file->is_directory = FALSE; return new_file; } /* deallocate a file object */ static void nntp_file_destroy (nntp_file* file) { GList *current_part; g_free(file->file_name); g_free(file->folder_name); current_part = file->part_list; while (current_part != NULL) { if (file->is_directory) { nntp_file_destroy((nntp_file*) current_part->data); } else { nntp_fragment_destroy((nntp_fragment*) current_part->data); } current_part = current_part->next; } g_list_free(file->part_list); g_free(file); } /* look up a fragment in a file */ static nntp_fragment* nntp_file_look_up_fragment (nntp_file *file, int fragment_number) { nntp_fragment *fragment; GList *current_fragment = file->part_list; while (current_fragment != NULL) { fragment = (nntp_fragment*) current_fragment->data; if (fragment->fragment_number == fragment_number) { return fragment; } current_fragment = current_fragment->next; } return NULL; } /* compute the total size of a file by adding up the size of its fragments, then scale down by 3/4 to account for * the uu or base64 encoding */ static int nntp_file_get_total_size (nntp_file *file) { nntp_fragment *fragment; int total_size; GList *current_fragment; total_size = 0; current_fragment = file->part_list; while (current_fragment != NULL) { fragment = (nntp_fragment*) current_fragment->data; total_size += fragment->fragment_size - 800; /* subtract out average header size */ current_fragment = current_fragment->next; } return 3 * total_size / 4; } /* add a fragment to a file */ static void nntp_file_add_part (nntp_file *file, int fragment_number, char* fragment_id, int fragment_size) { /* make sure we don't already have this fragment */ /* note: this isn't good for threads, where more than one message has the same subject line, * so we really should collect them instead of discarding all but the first */ if (nntp_file_look_up_fragment (file, fragment_number) == NULL) { nntp_fragment* fragment = nntp_fragment_new (fragment_number, fragment_id, fragment_size); file->part_list = g_list_append (file->part_list, fragment); } } /* look up a file object in the file list from its name; avoid lookup if same as last time (soon) */ static nntp_file* look_up_file (GList *file_list, char *filename, gboolean is_directory) { nntp_file* file; GList* current_file = file_list; while (current_file != NULL) { file = (nntp_file*) current_file->data; if (g_ascii_strcasecmp( file->file_name, filename) == 0 && file->is_directory == is_directory) { return file; } current_file = current_file->next; } return NULL; } /* start reading the article associated with the passed in fragment */ static GnomeVFSResult start_loading_article (NNTPConnection *conn, nntp_fragment *fragment) { GnomeVFSResult result; char *command_str; char *line = NULL; /* issue the command to fetch the article */ command_str = g_strdup_printf("BODY %s", fragment->fragment_id); result = do_control_write(conn, command_str); g_free(command_str); if (result != GNOME_VFS_OK) { return result; } /* read the response command, and check that it's the right number (eventually) */ result = read_response_line (conn, &line); g_free(line); if (result != GNOME_VFS_OK) { return result; } conn->request_in_progress = TRUE; return GNOME_VFS_OK; } /* utility routine to uudecode the passed in text. Translate every four 6-bit bytes to 3 8 bit ones */ static int uu_decode_text(char *text_buffer, int text_len) { int input_index, output_index; int byte_0, byte_1, byte_2, byte_3; input_index = 1; /* skip length byte */ output_index = 0; while (input_index < text_len) { byte_0 = text_buffer[input_index] - 32; byte_1 = text_buffer[input_index + 1] - 32; byte_2 = text_buffer[input_index + 2] - 32; byte_3 = text_buffer[input_index + 3] - 32; text_buffer[output_index] = ((byte_0 << 2) & 252) | ((byte_1 >> 4) & 3); text_buffer[output_index + 1] = ((byte_1 << 4) & 240) | ((byte_2 >> 2) & 15); text_buffer[output_index + 2] = ((byte_2 << 6) & 192) | (byte_3 & 63); input_index += 4; output_index += 3; } return output_index; } /* utility to decode the passed in text using base 64 */ static int base_64_map(char input_char) { if (input_char >= 'A' && input_char <= 'Z') { return input_char - 'A'; } if (input_char >= 'a' && input_char <= 'z') { return 26 + input_char - 'a'; } if (input_char >= '0' && input_char <= '9') { return 52 + input_char - '0'; } if (input_char == '+') { return 62; } if (input_char == '/') { return 63; } if (input_char == '=') { return 0; } /* not a valid character, so return -1 */ return -1; } static int base_64_decode_text(char* text_buffer, int text_len) { int input_index, output_index; int byte_0, byte_1, byte_2, byte_3; input_index = 1; /* skip length byte */ output_index = 0; while (input_index < text_len) { byte_0 = base_64_map(text_buffer[input_index]); byte_1 = base_64_map(text_buffer[input_index + 1]); byte_2 = base_64_map(text_buffer[input_index + 2]); byte_3 = base_64_map(text_buffer[input_index + 3]); /* if we hit a return, we're done */ if (text_buffer[input_index] < 32) { return output_index; } /* if there are any unmappable characters, skip this line */ if (byte_0 < 0 || byte_1 < 0 || byte_2 < 0 || byte_3 < 0) { return 0; } /* shift the bits into place and output them */ text_buffer[output_index] = ((byte_0 << 2) & 252) | ((byte_1 >> 4) & 3); text_buffer[output_index + 1] = ((byte_1 << 4) & 240) | ((byte_2 >> 2) & 15); text_buffer[output_index + 2] = ((byte_2 << 6) & 192) | (byte_3 & 63); input_index += 4; output_index += 3; } return output_index; } /* utility that returns true if all the characters in the passed in line are valide for uudecoding */ static gboolean line_in_decode_range(char* input_line) { char *line_ptr; char current_char; line_ptr = input_line; while (*line_ptr != '\0') { current_char = *line_ptr++; if (current_char < 32 || current_char > 95) { return FALSE; } } return TRUE; } /* fill the buffer from the passed in article fragment. If the first line flag is set, * test to see if uudecoding is required. */ static GnomeVFSResult load_from_article (NNTPConnection *conn, nntp_fragment *fragment, gboolean first_line_flag) { GnomeVFSResult result; char *line = NULL; char *dest_ptr; int buffer_offset; int line_len; /* loop, loading the article into the buffer */ buffer_offset = 0; while (buffer_offset < conn->buffer_size - 1024) { result = read_response_line (conn, &line); if (first_line_flag && !conn->uu_decode_mode && !conn->base_64_decode_mode) { /* FIXME: should apply additional checks here for the permission flags, etc */ if (strncmp(line, "begin ", 6) == 0) { conn->uu_decode_mode = TRUE; g_free (line); buffer_offset = 0; continue; } else if (strncmp(line, "Content-Transfer-Encoding: base64", 33) == 0) { conn->base_64_decode_mode = TRUE; g_free (line); buffer_offset = 0; continue; } else if (line[0] == 'M' && strlen(line) == 61 && line_in_decode_range(line)) { conn->uu_decode_mode = TRUE; buffer_offset = 0; } } if (line[0] != '.' && line[1] != '\r') { line_len = strlen(line); if (buffer_offset + line_len > conn->buffer_size) { g_message("Error! exceeded buffer! %d", buffer_offset + line_len); line_len = conn->buffer_size - buffer_offset; } dest_ptr = (char*) conn->buffer + buffer_offset; g_memmove(dest_ptr, line, line_len); if (conn->uu_decode_mode) { line_len = uu_decode_text(dest_ptr, line_len); buffer_offset += line_len; fragment->bytes_read += line_len; } else if (conn->base_64_decode_mode) { line_len = base_64_decode_text(dest_ptr, line_len); buffer_offset += line_len; fragment->bytes_read += line_len; } else { buffer_offset += line_len + 1; dest_ptr += line_len; *dest_ptr = '\n'; fragment->bytes_read += line_len + 1; } } else { g_free(line); conn->request_in_progress = FALSE; break; } g_free(line); } conn->amount_in_buffer = buffer_offset; conn->buffer_offset = 0; return GNOME_VFS_OK; } /* load data from the current file fragment, advancing to a new one if necessary */ static GnomeVFSResult load_file_fragment(NNTPConnection *connection) { nntp_fragment *fragment; GnomeVFSResult result; gboolean first_line_flag; first_line_flag = FALSE; if (!connection->request_in_progress) { if (connection->current_fragment == NULL) { connection->current_fragment = connection->current_file->part_list; first_line_flag = TRUE; } else { connection->current_fragment = connection->current_fragment->next; if (connection->current_fragment == NULL) { connection->eof_flag = TRUE; return GNOME_VFS_ERROR_EOF; } } fragment = (nntp_fragment *) connection->current_fragment->data; result = start_loading_article(connection, fragment); } if (connection->current_fragment == NULL) { connection->eof_flag = TRUE; return GNOME_VFS_ERROR_EOF; } fragment = (nntp_fragment *) connection->current_fragment->data; result = load_from_article(connection, fragment, first_line_flag); return result; } /* calculate the size of the data remaining in the buffer */ static int bytes_in_buffer(NNTPConnection *connection) { return connection->amount_in_buffer - connection->buffer_offset; } /* utility routine to move bytes from the fragment to the application */ static int copy_bytes_from_buffer(NNTPConnection *connection, gpointer destination_buffer, int bytes_to_read, GnomeVFSFileSize *bytes_read) { int size_to_move; size_to_move = bytes_in_buffer(connection); if (size_to_move == 0) { return 0; } if (bytes_to_read < size_to_move) { size_to_move = bytes_to_read; } /* move the bytes from the buffer */ g_memmove(destination_buffer, ((char*) connection->buffer) + connection->buffer_offset, size_to_move); /* update the counts */ connection->buffer_offset += size_to_move; *bytes_read += size_to_move; return size_to_move; } /* read a byte range from the active connection */ static GnomeVFSResult nntp_file_read(NNTPConnection *connection, gpointer buffer, GnomeVFSFileSize num_bytes, GnomeVFSFileSize *bytes_read) { int bytes_to_read; GnomeVFSResult result; /* loop, loading fragments and copying data until the request is fulfilled or * we're out of fragments */ bytes_to_read = num_bytes; *bytes_read = 0; while (bytes_to_read > 0) { bytes_to_read -= copy_bytes_from_buffer(connection, ((char*) buffer) + *bytes_read, bytes_to_read, bytes_read); if (bytes_to_read > bytes_in_buffer(connection)) { if (connection->eof_flag) { /* don't return EOF here as it will cause the last part to be discarded */ return GNOME_VFS_OK; } result = load_file_fragment(connection); } } return GNOME_VFS_OK; } /* deallocate the passed in file list */ static void free_nntp_file_list (GList *file_list) { GList* current_file; if (file_list == NULL) { return; } current_file = file_list; while (current_file != NULL) { nntp_file_destroy ((nntp_file*) current_file->data); current_file = current_file->next; } g_list_free (file_list); } /* utility to obtain authorization info from gnome-vfs */ static void get_auth_info (NNTPConnection *conn, char** user, char** password) { GnomeVFSResult res; GnomeVFSModuleCallbackAuthenticationIn in_args; GnomeVFSModuleCallbackAuthenticationOut out_args; *user = NULL; *password = NULL; memset (&in_args, 0, sizeof (in_args)); memset (&out_args, 0, sizeof (out_args)); in_args.uri = gnome_vfs_uri_to_string(conn->uri, 0); res = gnome_vfs_module_callback_invoke (GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION, &in_args, sizeof (in_args), &out_args, sizeof (out_args)); g_free(in_args.uri); *user = out_args.username; *password = out_args.password; } /* key routine to load the newsgroup overview and return a list of nntp_file objects. * It maintains a cache to avoid reloading. * * FIXME: for now, we use a single element cache, but eventually we want to cache multiple newsgroups * also, we eventually want to reload it if enough time has passed */ static GnomeVFSResult get_files_from_newsgroup (NNTPConnection *conn, const char* newsgroup_name, GList** result_file_list) { GnomeVFSResult result; char *group_command, *tmpstring; int first_message, last_message, total_messages; char *command_str; GList *file_list; gchar *user, *pass; /* see if we can load it from the cache */ if (current_newsgroup_name != NULL && g_ascii_strcasecmp (newsgroup_name, current_newsgroup_name) == 0) { *result_file_list = current_newsgroup_files; return GNOME_VFS_OK; } *result_file_list = NULL; /* we don't have it in the cache, so load it from the server */ if (current_newsgroup_name != NULL) { free_nntp_file_list (current_newsgroup_files); g_free (current_newsgroup_name); current_newsgroup_name = NULL; } group_command = g_strdup_printf ("GROUP %s", newsgroup_name); result = do_basic_command (conn, group_command); g_free (group_command); if (result != GNOME_VFS_OK || conn->response_code != 211) { /* if we're anonymous, prompt for a password and try that */ if (conn->anonymous) { get_auth_info(conn, &user, &pass); if (user != NULL) { conn->anonymous = FALSE; tmpstring = g_strdup_printf ("AUTHINFO user %s", user); result = do_basic_command (conn, tmpstring); g_free (tmpstring); if (IS_300 (conn->response_code)) { tmpstring = g_strdup_printf ("AUTHINFO pass %s", pass); result = do_basic_command (conn, tmpstring); g_free (tmpstring); group_command = g_strdup_printf ("GROUP %s", newsgroup_name); result = do_basic_command (conn, group_command); g_free (group_command); } } g_free (user); g_free (pass); } if (result != GNOME_VFS_OK || conn->response_code != 211) { g_message ("couldnt set group to %s, code %d", newsgroup_name, conn->response_code); return GNOME_VFS_ERROR_NOT_FOUND; /* could differentiate error better */ } } sscanf (conn->response_message, "%d %d %d", &total_messages, &first_message, &last_message); /* read in the header and build the file list for the connection */ if ((last_message - first_message) > MAX_MESSAGE_COUNT) { first_message = last_message - MAX_MESSAGE_COUNT; } command_str = g_strdup_printf ("XOVER %d-%d", first_message, last_message); file_list = assemble_files_from_overview (conn, command_str); g_free (command_str); current_newsgroup_name = g_strdup (newsgroup_name); current_newsgroup_files = file_list; *result_file_list = file_list; return GNOME_VFS_OK; } /* utility to strip leading and trailing slashes from a string. It frees the input string and * returns a new one */ static char* strip_slashes (char* source_string) { char *temp_str, *result_str; int last_offset; temp_str = source_string; if (temp_str[0] == '/') { temp_str += 1; } last_offset = strlen(temp_str) - 1; if (temp_str[last_offset] == '/') { temp_str[last_offset] = '\0'; } result_str = g_strdup(temp_str); g_free(source_string); return result_str; } /* parse the uri to extract the newsgroup name and the file name */ static void extract_newsgroup_and_filename(GnomeVFSURI *uri, char** newsgroup, char **directory, char **filename) { char *dirname, *slash_pos; *filename = gnome_vfs_unescape_string(gnome_vfs_uri_extract_short_name (uri), ""); *directory = NULL; dirname = strip_slashes(gnome_vfs_uri_extract_dirname(uri)); *newsgroup = gnome_vfs_unescape_string (dirname, ""); slash_pos = strchr (*newsgroup, '/'); if (slash_pos != NULL) { *slash_pos = '\0'; *directory = g_strdup (slash_pos + 1); } g_free (dirname); } /* fetch the nntp_file object from the passed in uri */ static nntp_file* nntp_file_from_uri (NNTPConnection *conn, GnomeVFSURI *uri) { GnomeVFSResult result; char *newsgroup_name, *file_name, *directory_name; nntp_file *file; GList *file_list; /* extract the newsgroup name */ extract_newsgroup_and_filename(uri, &newsgroup_name, &directory_name, &file_name); /* load the (cached) file list */ result = get_files_from_newsgroup (conn, newsgroup_name, &file_list); if (file_list == NULL) { file = NULL; } else { /* parse the uri into the newsgroup and file */ if (directory_name != NULL) { file = look_up_file (file_list, directory_name, TRUE); if (file != NULL) { file = look_up_file (file->part_list, file_name, FALSE); } } else { file = look_up_file (file_list, file_name, FALSE); } } g_free(newsgroup_name); g_free(file_name); g_free(directory_name); return file; } /* determine if a file has all of it's fragments */ static gboolean has_all_fragments (nntp_file *file) { return g_list_length (file->part_list) >= file->total_parts; } /* add a file fragment to the file list, creating a new file object if necessary */ static GList *add_file_fragment(GList* file_list, char* filename, char* folder_name, char* fragment_id, int fragment_size, int part_number, int part_total, time_t mod_date) { nntp_file *base_file; /* don't use part 0 */ if (part_number == 0) { return file_list; } /* get the file object associated with the filename if any */ base_file = look_up_file(file_list, filename, FALSE); if (base_file == NULL) { base_file = nntp_file_new(filename, folder_name, part_total); base_file->mod_date = mod_date; file_list = g_list_append(file_list, base_file); } /* add the fragment to the file */ nntp_file_add_part(base_file, part_number, fragment_id, fragment_size); return file_list; } /* remove any partial files from the file list */ static GList *remove_partial_files (GList *file_list) { nntp_file* file; GList* next_file; GList* current_file = file_list; while (current_file != NULL) { next_file = current_file->next; file = (nntp_file*) current_file->data; if (!has_all_fragments (file)) { file_list = g_list_remove_link (file_list, current_file); nntp_file_destroy (file); } current_file = next_file; } return file_list; } /* loop through the file list to update the size fields */ static void update_file_sizes(GList *file_list) { nntp_file* file; GList* current_file = file_list; while (current_file != NULL) { file = (nntp_file*) current_file->data; file->file_size = nntp_file_get_total_size(file); current_file = current_file->next; } } /* the following set of routines are used to group the files into psuedo-directories by using * a hash table containing lists of nntp_file objects. */ /* add the passed in file's foldername to the hash table */ static void add_file_to_folder (GHashTable *folders, nntp_file *file) { GList *folder_contents; if (file->folder_name == NULL) { return; } folder_contents = g_hash_table_lookup (folders, file->folder_name); if (folder_contents != NULL) { g_list_append(folder_contents, file); } else { folder_contents = g_list_append(NULL, file); g_hash_table_insert (folders, g_strdup(file->folder_name), folder_contents); } } /* remove non-singleton files that are contained in folders from the file list */ static void remove_file_from_list (gpointer key, gpointer value, gpointer callback_data) { GList *element_list; nntp_file* file; GList** file_list_ptr; file_list_ptr = (GList**) callback_data; element_list = (GList*) value; /* if there is more than one element in the list, remove all of them from the file list */ if (element_list != NULL && g_list_length(element_list) > 1) { while (element_list != NULL) { file = (nntp_file*) element_list->data; *file_list_ptr = g_list_remove(*file_list_ptr, element_list->data); element_list = element_list->next; } } } static void remove_contained_files(GHashTable *folders, GList** file_list_ptr) { /* iterate through the passed in hash table to find the files to remove */ g_hash_table_foreach (folders, remove_file_from_list, file_list_ptr); } /* utility routine to calculate a folder's mod-date by inspecting the dates of it's constituents */ static void calculate_folder_mod_date (nntp_file *folder) { time_t latest_mod_date; GList *current_file; nntp_file* current_file_data; latest_mod_date = 0; current_file = folder->part_list; while (current_file != NULL) { current_file_data = (nntp_file*) current_file->data; if (current_file_data->mod_date > latest_mod_date) { latest_mod_date = current_file_data->mod_date; } current_file = current_file->next; } folder->mod_date = latest_mod_date; } /* generate folder objects from the entries in the hash table */ static void generate_folder_from_element (gpointer key, gpointer value, gpointer callback_data) { GList *element_list; int number_of_elements; nntp_file *new_folder; char* key_as_string; GList** file_list_ptr; file_list_ptr = (GList**) callback_data; element_list = (GList*) value; key_as_string = (char*) key; number_of_elements = g_list_length(element_list); if (number_of_elements > 1) { if (strlen(key_as_string) == 0) { key_as_string = "Unknown Title"; } new_folder = nntp_file_new (key_as_string, NULL, number_of_elements); new_folder->is_directory = TRUE; new_folder->file_type = g_strdup ("x-directory/normal"); new_folder->part_list = g_list_copy (element_list); calculate_folder_mod_date (new_folder); *file_list_ptr = g_list_append (*file_list_ptr, new_folder); } } static void generate_folders(GHashTable *folders, GList** file_list_ptr) { /* iterate through the passed in hash table to generate a folder for each non-singleton entry */ g_hash_table_foreach (folders, generate_folder_from_element, file_list_ptr); } /* helper routine to deallocate hash table elements */ static gboolean destroy_element (gpointer key, gpointer value, gpointer data) { g_free(key); g_list_free((GList*) value); return TRUE; } /* deallocate the data structures associate with the folder hash table */ static void destroy_folder_hash(GHashTable *folders) { /* iterate through the hash table to destroy the lists it contains, before destroying *the table itself */ g_hash_table_foreach_remove (folders, destroy_element, NULL); g_hash_table_destroy(folders); } /* walk through the file list and try to generate folders to group files with similar subjects */ static GList* assemble_folders (GList *file_list) { GList *current_item; nntp_file *current_file; GHashTable *folders; GList* file_list_ptr; /* need indirection for file list head */ file_list_ptr = file_list; /* make a pass through the files grouping the ones with matching foldernames. Use a hash * table for fast lookups */ folders = g_hash_table_new (g_str_hash, g_str_equal); current_item = file_list_ptr; while (current_item != NULL) { current_file = (nntp_file*) current_item->data; if (current_file->folder_name != NULL) { add_file_to_folder (folders, current_file); } current_item = current_item->next; } /* make a pass through the folder list to remove contained files from the file list */ remove_contained_files (folders, &file_list_ptr); /* generate folders from the hash table (not singletons) */ generate_folders (folders, &file_list_ptr); /* deallocate resources now that we're done */ destroy_folder_hash (folders); return file_list_ptr; } /* this is the main loop for assembling the files. It issues the passed in * overview command and then reads the headers one at a time, processing the * fragments into a list of files */ static GList* assemble_files_from_overview (NNTPConnection *conn, char *command) { GnomeVFSResult result; char *line = NULL; int message_size; int part_number, total_parts; char* filename, *folder_name, *message_id; GList *file_list; time_t mod_date; file_list = NULL; /* issue the overview command to the server */ result = do_control_write (conn, command); if (result != GNOME_VFS_OK) { return file_list; } /* read the response command, and check that it's the right number (eventually) */ result = read_response_line (conn, &line); g_free(line); if (result != GNOME_VFS_OK) { return file_list; } while (TRUE) { result = read_response_line (conn, &line); if (line[0] != '.' && line[1] != '\r') { if (parse_header (line, &filename, &folder_name, &message_id, &message_size, &part_number, &total_parts, &mod_date)) { file_list = add_file_fragment (file_list, filename, folder_name, message_id, message_size, part_number, total_parts, mod_date); g_free (filename); g_free (folder_name); g_free (message_id); } } else { break; } g_free(line); } file_list = remove_partial_files (file_list); update_file_sizes (file_list); file_list = assemble_folders (file_list); return file_list; } /* newsgroup files aren't local */ gboolean do_is_local (GnomeVFSMethod *method, const GnomeVFSURI *uri) { return FALSE; } static void prepare_to_read_file (NNTPConnection *conn, nntp_file *file) { conn->current_file = file; conn->current_fragment = NULL; conn->buffer_offset = 0; conn->amount_in_buffer = 0; conn->eof_flag = FALSE; conn->uu_decode_mode = FALSE; conn->base_64_decode_mode = FALSE; nntp_connection_reset_buffer (conn); } static GnomeVFSResult do_open (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSOpenMode mode, GnomeVFSContext *context) { GnomeVFSResult result; NNTPConnection *conn; nntp_file *file; const char* basename; /* we can save allocating a connection to search for the .directory file */ basename = gnome_vfs_uri_extract_short_name(uri); if (strcmp(basename, ".directory") == 0) { return GNOME_VFS_ERROR_NOT_FOUND; } result = nntp_connection_acquire (uri, &conn, context); if (result != GNOME_VFS_OK) return result; /* make sure we have the file */ file = nntp_file_from_uri(conn, uri); if (file == NULL) { nntp_connection_release (conn); return GNOME_VFS_ERROR_NOT_FOUND; } else { prepare_to_read_file (conn, file); } if (result == GNOME_VFS_OK) { *method_handle = (GnomeVFSMethodHandle *) conn; } else { *method_handle = NULL; nntp_connection_release (conn); } return result; } static GnomeVFSResult do_close (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSContext *context) { NNTPConnection *conn = (NNTPConnection *) method_handle; nntp_connection_release (conn); return GNOME_VFS_OK; } static GnomeVFSResult do_read (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, gpointer buffer, GnomeVFSFileSize num_bytes, GnomeVFSFileSize *bytes_read, GnomeVFSContext *context) { NNTPConnection *conn = (NNTPConnection * )method_handle; return nntp_file_read(conn, buffer, num_bytes, bytes_read); } static GnomeVFSResult do_get_file_info (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { const char* host_name; const char* temp_str, *first_slash; GnomeVFSURI *parent = gnome_vfs_uri_get_parent (uri); GnomeVFSResult result; host_name = gnome_vfs_uri_get_host_name(uri); if (host_name == NULL) { return GNOME_VFS_ERROR_INVALID_HOST_NAME; } /* if it's the top level newsgroup, treat it like a directory */ temp_str = gnome_vfs_uri_get_path(uri); first_slash = strchr(temp_str + 1, '/'); if (parent == NULL || first_slash == NULL) { /* this is a request for info about the root directory */ file_info->name = g_strdup("/"); file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY; file_info->mime_type = g_strdup ("x-directory/normal"); file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ | GNOME_VFS_PERM_OTHER_READ; file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE | GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE | GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS; return GNOME_VFS_OK; } else { GnomeVFSMethodHandle *method_handle; char *name; result = do_open_directory (method, &method_handle, parent, options, context); gnome_vfs_uri_unref (parent); if (result != GNOME_VFS_OK) { return result; } name = gnome_vfs_uri_extract_short_name (uri); while (result == GNOME_VFS_OK) { result = do_read_directory (method, method_handle, file_info, context); if (result == GNOME_VFS_OK) { if (file_info->name && !strcmp (file_info->name, name)) { g_free (name); do_close_directory( method, method_handle, context); return GNOME_VFS_OK; } gnome_vfs_file_info_clear (file_info); } } do_close_directory(method, method_handle, context); } return GNOME_VFS_ERROR_NOT_FOUND; } static GnomeVFSResult do_get_file_info_from_handle (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { return do_get_file_info (method, ((NNTPConnection *)method_handle)->uri, file_info, options, context); } static GnomeVFSResult do_open_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { const char *newsgroup_server; char *newsgroup_name; char *directory_name, *mapped_dirname; NNTPConnection *conn; GnomeVFSResult result; nntp_file *file; GList *file_list; newsgroup_server = gnome_vfs_uri_get_host_name (uri); newsgroup_name = gnome_vfs_uri_extract_dirname (uri); directory_name = g_strdup (gnome_vfs_uri_extract_short_name (uri)); if (strcmp (newsgroup_name, "/") == 0 || strlen(newsgroup_name) == 0) { g_free (newsgroup_name); newsgroup_name = directory_name; directory_name = NULL; } if (newsgroup_name == NULL) { g_free(directory_name); return GNOME_VFS_ERROR_NOT_FOUND; } newsgroup_name = strip_slashes (newsgroup_name); result = nntp_connection_acquire (uri, &conn, context); if (result != GNOME_VFS_OK) { g_free (newsgroup_name); g_free (directory_name); return result; } /* get the files from the newsgroup */ result = get_files_from_newsgroup (conn, newsgroup_name, &file_list); if (result != GNOME_VFS_OK) { g_free (newsgroup_name); g_free (directory_name); nntp_connection_release(conn); return result; } if (directory_name == NULL) { conn->next_file = file_list; } else { if (file_list == NULL) { file = NULL; } else { mapped_dirname = gnome_vfs_unescape_string (directory_name, ""); file = look_up_file (file_list, mapped_dirname, TRUE); g_free (mapped_dirname); } if (file == NULL) { g_message ("couldnt find file %s", directory_name); return GNOME_VFS_ERROR_NOT_FOUND; } if (file->is_directory) { conn->next_file = file->part_list; } else { conn->next_file = NULL; } } if (result != GNOME_VFS_OK) { g_message ("couldnt set group!"); nntp_connection_release (conn); g_free (newsgroup_name); g_free (directory_name); return result; } *method_handle = (GnomeVFSMethodHandle *) conn; g_free (newsgroup_name); g_free (directory_name); return result; } static GnomeVFSResult do_close_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSContext *context) { NNTPConnection *conn = (NNTPConnection *) method_handle; nntp_connection_release (conn); return GNOME_VFS_OK; } static GnomeVFSResult do_read_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileInfo *file_info, GnomeVFSContext *context) { NNTPConnection *conn = (NNTPConnection *) method_handle; nntp_file *file_data; const char* mime_string; if (!conn->next_file) return GNOME_VFS_ERROR_EOF; /* fill out the information for the current file, and bump the pointer */ gnome_vfs_file_info_clear(file_info); /* implement the size threshold by skipping files smaller than the threshold */ file_data = (nntp_file*) conn->next_file->data; while (file_data->file_size < MIN_FILE_SIZE_THRESHOLD && !file_data->is_directory) { conn->next_file = conn->next_file->next; if (conn->next_file == NULL) { return GNOME_VFS_ERROR_EOF; } else { file_data = (nntp_file*) conn->next_file->data; } } file_info->name = g_strdup(file_data->file_name); file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ | GNOME_VFS_PERM_USER_WRITE |GNOME_VFS_PERM_OTHER_READ; file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE | GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE | GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS | GNOME_VFS_FILE_INFO_FIELDS_MTIME; /* handle the case when it's a directory */ if (file_data->is_directory) { file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY; file_info->mime_type = g_strdup ("x-directory/normal"); file_info->mtime = file_data->mod_date; file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ | GNOME_VFS_PERM_USER_WRITE | GNOME_VFS_PERM_OTHER_READ | GNOME_VFS_PERM_USER_EXEC | GNOME_VFS_PERM_GROUP_EXEC | GNOME_VFS_PERM_OTHER_EXEC; } else { file_info->type = GNOME_VFS_FILE_TYPE_REGULAR; file_info->mtime = file_data->mod_date; /* figure out the mime type from the extension; if it's unrecognized, use "text/plain" * instead of "application/octet-stream" */ mime_string = gnome_vfs_mime_type_from_name(file_data->file_name); if (strcmp(mime_string, "application/octet-stream") == 0) { file_info->mime_type = g_strdup("text/plain"); } else { file_info->mime_type = g_strdup(mime_string); } file_info->size = file_data->file_size; file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SIZE; } conn->next_file = conn->next_file->next; return GNOME_VFS_OK; } static GnomeVFSResult do_check_same_fs (GnomeVFSMethod *method, GnomeVFSURI *a, GnomeVFSURI *b, gboolean *same_fs_return, GnomeVFSContext *context) { *same_fs_return = nntp_connection_uri_equal (a,b); return GNOME_VFS_OK; } static GnomeVFSMethod method = { sizeof (GnomeVFSMethod), do_open, NULL, /* create */ do_close, do_read, NULL, /* write */ NULL, /* seek */ NULL, /* tell */ NULL, /* truncate */ do_open_directory, do_close_directory, do_read_directory, do_get_file_info, do_get_file_info_from_handle, do_is_local, NULL, /* make_directory */ NULL, /* remove directory */ NULL, /* move */ NULL, /* unlink */ do_check_same_fs, NULL, /* set_file_info */ NULL, /* truncate */ NULL, /* find_directory */ NULL /* create_symbolic_link */ }; GnomeVFSMethod * vfs_module_init (const char *method_name, const char *args) { #ifdef HAVE_GCONF char *argv[] = {"vfs-nntp-method"}; int argc = 1; /* Ensure GConf is initialized. If more modules start to rely on * GConf, then this should probably be moved into a more * central location */ if (!gconf_is_initialized ()) { /* auto-initializes OAF if necessary */ gconf_init (argc, argv, NULL); } #endif return &method; } void vfs_module_shutdown (GnomeVFSMethod *method) { }