do_get_file_info(): Fixed ElectricFence SIGSEGV
[gnome-vfs-httpcaptive.git] / modules / http-method.c
index 0c4d49e..4aa317d 100644 (file)
 #include <unistd.h>
 #include <netdb.h>
 
+#ifndef HAVE_G_STR_HAS_SUFFIX
+gboolean g_str_has_suffix(const gchar *str, const gchar *suffix);
+#endif
+
 #ifdef DEBUG_HTTP_ENABLE
 void
 http_debug_printf (char *fmt, ...)
@@ -100,6 +104,9 @@ http_debug_printf (char *fmt, ...)
 /* Standard HTTP proxy port */
 #define DEFAULT_HTTP_PROXY_PORT 8080
 
+/* Maximum amount of data to read if seek()ed forward. Otherwise make a new connection. */
+#define MAX_BUFFER_SEEK_SKIP_READ      0x10000
+
 /* GConf paths and keys */
 #define PATH_GCONF_GNOME_VFS "/system/http_proxy"
 #define ITEM_GCONF_HTTP_PROXY_PORT "port"
@@ -211,8 +218,11 @@ typedef struct {
        /* File info for this file */
        GnomeVFSFileInfo *file_info;
 
-       /* Bytes read so far.  */
-       GnomeVFSFileSize bytes_read;
+       /* File offset of current pointer of 'socket_buffer'. */
+       GnomeVFSFileOffset socket_buffer_offset;
+
+       /* Offset; Current file position. */
+       GnomeVFSFileOffset offset;
 
        /* Bytes to be written... */
        GByteArray *to_be_written;
@@ -283,29 +293,46 @@ defaults_file_info_new (void)
        return ret;
 }
 
+/* Do not allocate the 'handle' memory. */
+static void
+http_file_handle_init (HttpFileHandle *handle,
+                      GnomeVFSSocketBuffer *socket_buffer,
+                      GnomeVFSURI *uri)
+{
+       memset (handle, 0, sizeof (*handle));
+
+       handle->socket_buffer = socket_buffer;
+       handle->uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE );
+       handle->uri = uri;
+       gnome_vfs_uri_ref(handle->uri);
+
+       handle->file_info = defaults_file_info_new();
+       handle->file_info->name = gnome_vfs_uri_extract_short_name (uri);
+}
+
+static HttpFileHandle *
+http_file_handle_alloc (void)
+{
+       return g_new0 (HttpFileHandle, 1);
+}
+
 static HttpFileHandle *
 http_file_handle_new (GnomeVFSSocketBuffer *socket_buffer,
                      GnomeVFSURI *uri)
 {
        HttpFileHandle *result;
 
-       result = g_new0 (HttpFileHandle, 1);
-
-       result->socket_buffer = socket_buffer;
-       result->uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE );
-       result->uri = uri;
-       gnome_vfs_uri_ref(result->uri);
-
-       result->file_info = defaults_file_info_new();
-       result->file_info->name = gnome_vfs_uri_extract_short_name (uri);
+       result = http_file_handle_alloc ();
+       http_file_handle_init (result, socket_buffer, uri);
 
        return result;
 }
 
+/* Does not free the 'handle' memory. */
 static void
-http_file_handle_destroy (HttpFileHandle *handle)
+http_file_handle_clear (HttpFileHandle *handle)
 {
-       if (handle == NULL) {
+       if (handle == NULL || handle->uri == NULL) {
                return;
        }
 
@@ -322,6 +349,19 @@ http_file_handle_destroy (HttpFileHandle *handle)
        g_list_foreach(handle->files, (GFunc)gnome_vfs_file_info_unref, NULL);
        g_list_free(handle->files);
 
+       /* Structure is now fully cleared: */
+       memset (handle, 0, sizeof (*handle));
+}
+
+static void
+http_file_handle_destroy (HttpFileHandle *handle)
+{
+       if (handle == NULL) {
+               return;
+       }
+
+       http_file_handle_clear (handle);
+
        g_free (handle);
 }
 
@@ -649,22 +689,22 @@ get_header (GnomeVFSSocketBuffer *socket_buffer,
 
 /* rename this function? */
 static GnomeVFSResult
-create_handle (GnomeVFSURI *uri,
-              GnomeVFSSocketBuffer *socket_buffer,
-              GnomeVFSContext *context,
-              /* OUT */ HttpFileHandle **p_handle)
+init_handle (GnomeVFSURI *uri,
+            GnomeVFSSocketBuffer *socket_buffer,
+            GnomeVFSContext *context,
+            /* OUT */ HttpFileHandle *handle)
 {
        GString *header_string;
        GnomeVFSResult result;
        guint server_status;
 
-       g_return_val_if_fail (p_handle != NULL, GNOME_VFS_ERROR_INTERNAL);
+       g_return_val_if_fail (handle != NULL, GNOME_VFS_ERROR_INTERNAL);
 
-       *p_handle = http_file_handle_new (socket_buffer, uri);
+       http_file_handle_init (handle, socket_buffer, uri);
 
        header_string = g_string_new (NULL);
 
-       ANALYZE_HTTP ("==> +create_handle");
+       ANALYZE_HTTP ("==> +init_handle");
 
        /* This is the status report string, which is the first header.  */
        result = get_header (socket_buffer, header_string);
@@ -678,9 +718,9 @@ create_handle (GnomeVFSURI *uri,
                goto error;
        }
 
-       (*p_handle)->server_status = server_status;
+       handle->server_status = server_status;
 
-       ANALYZE_HTTP ("==> +create_handle: fetching headers");
+       ANALYZE_HTTP ("==> +init_handle: fetching headers");
 
        /* Header fetching loop.  */
        for (;;) {
@@ -694,7 +734,7 @@ create_handle (GnomeVFSURI *uri,
                        break;
                }
 
-               (*p_handle)->response_headers = g_list_prepend ((*p_handle)->response_headers, 
+               handle->response_headers = g_list_prepend (handle->response_headers, 
                                                        g_strdup (header_string->str));
 
                /* We don't really care if we successfully parse the
@@ -704,12 +744,12 @@ create_handle (GnomeVFSURI *uri,
                 * past we would return NOT_FOUND if any header could
                 * not be parsed, but that seems wrong.
                 */
-               parse_header (*p_handle, header_string->str);
+               parse_header (handle, header_string->str);
        }
 
-       invoke_callback_headers_received (*p_handle);
+       invoke_callback_headers_received (handle);
 
-       ANALYZE_HTTP ("==> -create_handle: fetching headers");
+       ANALYZE_HTTP ("==> -init_handle: fetching headers");
 
        if (result != GNOME_VFS_OK) {
                goto error;
@@ -724,7 +764,7 @@ create_handle (GnomeVFSURI *uri,
  error:
        g_string_free (header_string, TRUE);
 
-       ANALYZE_HTTP ("==> -create_handle");
+       ANALYZE_HTTP ("==> -init_handle");
        return result;
 }
 
@@ -1498,8 +1538,9 @@ error:
        return result;
 }
 
+/* Do not allocate the 'handle' memory. */
 static GnomeVFSResult
-make_request (HttpFileHandle **handle_return,
+init_request (HttpFileHandle *handle,
              GnomeVFSURI *uri,
              const gchar *method,
              GByteArray *data,
@@ -1514,10 +1555,9 @@ make_request (HttpFileHandle **handle_return,
        char *authn_header_request;
        char *authn_header_proxy;
 
-       g_return_val_if_fail (handle_return != NULL, GNOME_VFS_ERROR_INTERNAL);
-       *handle_return = NULL;
+       g_return_val_if_fail (handle != NULL, GNOME_VFS_ERROR_INTERNAL);
 
-       ANALYZE_HTTP ("==> +make_request");
+       ANALYZE_HTTP ("==> +init_request");
 
        request                 = NULL;
        proxy_connect           = FALSE;
@@ -1593,33 +1633,31 @@ make_request (HttpFileHandle **handle_return,
                }
 
                /* Read the headers and create our internal HTTP file handle.  */
-               result = create_handle (uri, socket_buffer, context, handle_return);
+               result = init_handle (uri, socket_buffer, context, handle);
 
                if (result == GNOME_VFS_OK) {
                        socket_buffer = NULL;
                        break;
                }
-               if ((*handle_return)->server_status == HTTP_STATUS_UNAUTHORIZED) {
-                       if (! check_authn_retry_request (*handle_return, AuthnHeader_WWW, authn_header_request)) {
+               if (handle->server_status == HTTP_STATUS_UNAUTHORIZED) {
+                       if (! check_authn_retry_request (handle, AuthnHeader_WWW, authn_header_request)) {
                                break;
                        }
-               } else if ((*handle_return)->server_status == HTTP_STATUS_PROXY_AUTH_REQUIRED) {
-                       if (! check_authn_retry_request (*handle_return, AuthnHeader_WWW, authn_header_proxy)) {
+               } else if (handle->server_status == HTTP_STATUS_PROXY_AUTH_REQUIRED) {
+                       if (! check_authn_retry_request (handle, AuthnHeader_WWW, authn_header_proxy)) {
                                break;
                        }
                } else {
                        break;
                }
-               http_file_handle_destroy (*handle_return);
-               *handle_return = NULL;
+               http_file_handle_clear (handle);
        }
 
        g_free (authn_header_request);
        g_free (authn_header_proxy);
 
-       if (result != GNOME_VFS_OK && *handle_return != NULL) {
-               http_file_handle_destroy (*handle_return);
-               *handle_return = NULL;
+       if (result != GNOME_VFS_OK) {
+               http_file_handle_clear (handle);
        }
 
        if (request != NULL) {
@@ -1630,15 +1668,37 @@ make_request (HttpFileHandle **handle_return,
                gnome_vfs_socket_buffer_destroy (socket_buffer, TRUE);
        }
        
-       ANALYZE_HTTP ("==> -make_request");
+       ANALYZE_HTTP ("==> -init_request");
+       return result;
+}
+
+static GnomeVFSResult
+make_request (HttpFileHandle **handle_return,
+             GnomeVFSURI *uri,
+             const gchar *method,
+             GByteArray *data,
+             gchar *extra_headers,
+             GnomeVFSContext *context)
+{
+       GnomeVFSResult result;
+
+       g_return_val_if_fail (handle_return != NULL, GNOME_VFS_ERROR_INTERNAL);
+
+       *handle_return = http_file_handle_alloc ();
+       result = init_request (*handle_return, uri, method, data, extra_headers, context);
+       if (result != GNOME_VFS_OK) {
+               http_file_handle_destroy (*handle_return);
+               *handle_return = NULL;
+       }
+
        return result;
 }
 
 static void
-http_handle_close (HttpFileHandle *handle, 
+http_handle_clear (HttpFileHandle *handle, 
                   GnomeVFSContext *context)
 {
-       ANALYZE_HTTP ("==> +http_handle_close");
+       ANALYZE_HTTP ("==> +http_handle_clear");
        
        if (handle != NULL) {
                if (handle->socket_buffer) {
@@ -1648,15 +1708,20 @@ http_handle_close (HttpFileHandle *handle,
                        handle->socket_buffer = NULL;
                }
 
-               http_file_handle_destroy (handle);
+               http_file_handle_clear (handle);
        }
        
-       ANALYZE_HTTP ("==> -http_handle_close");
+       ANALYZE_HTTP ("==> -http_handle_clear");
 }
 
-typedef struct {
-       HttpFileHandle *http_file_handle;
-       } GnomeVFSHttpMethodHandle;
+static void
+http_handle_close (HttpFileHandle *handle, 
+                  GnomeVFSContext *context)
+{
+       http_handle_clear (handle, context);
+
+       http_file_handle_destroy (handle);
+}
 
 static GnomeVFSResult
 do_open (GnomeVFSMethod *method,
@@ -1666,7 +1731,6 @@ do_open (GnomeVFSMethod *method,
         GnomeVFSContext *context)
 {
        HttpFileHandle *handle;
-       GnomeVFSHttpMethodHandle *http_method_handle;
        GnomeVFSResult result = GNOME_VFS_OK;
        
        g_return_val_if_fail (uri->parent == NULL, GNOME_VFS_ERROR_INVALID_URI);
@@ -1685,9 +1749,7 @@ do_open (GnomeVFSMethod *method,
                handle = http_file_handle_new(NULL, uri); /* shrug */
        }
        if (result == GNOME_VFS_OK) {
-               http_method_handle = g_new (GnomeVFSHttpMethodHandle, 1);
-               http_method_handle->http_file_handle = handle;
-               *method_handle = (GnomeVFSMethodHandle *) http_method_handle;
+               *method_handle = (GnomeVFSMethodHandle *) handle;
        } else {
                *method_handle = NULL;
        }
@@ -1782,7 +1844,6 @@ do_close (GnomeVFSMethod *method,
          GnomeVFSMethodHandle *method_handle,
          GnomeVFSContext *context)
 {
-       GnomeVFSHttpMethodHandle *http_method_handle;
        HttpFileHandle *old_handle;
        HttpFileHandle *new_handle;
        GnomeVFSResult result;
@@ -1790,10 +1851,8 @@ do_close (GnomeVFSMethod *method,
        ANALYZE_HTTP ("==> +do_close");
        DEBUG_HTTP (("+Close handle:0x%08x", (unsigned int)method_handle));
 
-       http_method_handle = (GnomeVFSHttpMethodHandle *) method_handle;
-       old_handle = http_method_handle->http_file_handle;
-       g_free (http_method_handle);
-
+       old_handle = (HttpFileHandle *) method_handle;
+       
        /* if the handle was opened in write mode then:
         * 1) there won't be a connection open, and
         * 2) there will be data to_be_written...
@@ -1846,20 +1905,23 @@ do_write (GnomeVFSMethod *method,
          GnomeVFSMethodHandle *method_handle,
          gconstpointer buffer,
          GnomeVFSFileSize num_bytes,
-         GnomeVFSFileSize *bytes_read,
+         GnomeVFSFileSize *bytes_written,
          GnomeVFSContext *context)
 {
        HttpFileHandle *handle;
 
        DEBUG_HTTP (("+Write handle:0x%08x", (unsigned int)method_handle));
 
-       handle = ((GnomeVFSHttpMethodHandle *) method_handle)->http_file_handle;
+       handle = (HttpFileHandle *) method_handle;
+
+       if (handle->offset != 0)
+               return GNOME_VFS_ERROR_NOT_SUPPORTED;
 
        if(handle->to_be_written == NULL) {
                handle->to_be_written = g_byte_array_new();
        }
        handle->to_be_written = g_byte_array_append(handle->to_be_written, buffer, num_bytes);
-       *bytes_read = num_bytes;
+       *bytes_written = num_bytes;
        
        DEBUG_HTTP (("-Write (0)"));
        
@@ -1881,15 +1943,59 @@ do_read (GnomeVFSMethod *method,
        ANALYZE_HTTP ("==> +do_read");
        DEBUG_HTTP (("+Read handle=0x%08x", (unsigned int) method_handle));
 
-       handle = ((GnomeVFSHttpMethodHandle *) method_handle)->http_file_handle;
+       handle = (HttpFileHandle *) method_handle;
 
        if (handle->file_info->flags & GNOME_VFS_FILE_INFO_FIELDS_SIZE) {
                GnomeVFSFileSize max_bytes;
 
-               max_bytes = handle->file_info->size - handle->bytes_read;
+               max_bytes = MAX (0, handle->file_info->size - handle->offset);
                num_bytes = MIN (max_bytes, num_bytes);
        }
 
+       if (!num_bytes) {
+               *bytes_read = 0;
+               return GNOME_VFS_ERROR_EOF;
+       }
+
+       if (1
+           && handle->offset >  handle->socket_buffer_offset
+           && handle->offset <= handle->socket_buffer_offset+MAX_BUFFER_SEEK_SKIP_READ) {
+               static char drop_buffer[0x1000];
+               GnomeVFSFileSize bytes, bytes_read;
+               GnomeVFSResult result;
+
+               while ((bytes=MIN(sizeof(drop_buffer), handle->offset - handle->socket_buffer_offset))) {
+                       result = gnome_vfs_socket_buffer_read (handle->socket_buffer, drop_buffer, 
+                                       bytes, &bytes_read);
+                       if (result != GNOME_VFS_OK)
+                               return result;
+                       handle->socket_buffer_offset += bytes_read;
+               }
+       }
+
+       if (handle->offset != handle->socket_buffer_offset) {
+               GnomeVFSURI *uri = handle->uri;
+               gchar *extra_headers;
+               GnomeVFSFileOffset offset_save;
+
+               offset_save = handle->offset;
+               gnome_vfs_uri_ref(uri);
+               http_handle_clear (handle, context);
+               /* 'handle->offset' is already destroyed here: */
+               extra_headers = g_strdup_printf("Range: bytes=%" G_GINT64_FORMAT "-\r\n",(gint64)offset_save);
+               result = init_request (handle, uri, "GET", NULL, extra_headers,
+                                      context);
+               g_free (extra_headers);
+               gnome_vfs_uri_unref(uri);
+               handle->offset = offset_save;
+               if (result != GNOME_VFS_OK) {
+                       /* FIXME: 'method_handle' is now broken! */
+                       memset(handle, 0, sizeof (*handle));
+                       return result;
+               }
+               handle->socket_buffer_offset = handle->offset;
+       }
+
        result = gnome_vfs_socket_buffer_read (handle->socket_buffer, buffer, 
                        num_bytes, bytes_read);
        
@@ -1897,7 +2003,8 @@ do_read (GnomeVFSMethod *method,
                return GNOME_VFS_ERROR_EOF;
        }                                      
 
-       handle->bytes_read += *bytes_read;
+       handle->socket_buffer_offset += *bytes_read;
+       handle->offset += *bytes_read;
 
        DEBUG_HTTP (("-Read (%d)", result));
        ANALYZE_HTTP ("==> -do_read");
@@ -1905,6 +2012,53 @@ do_read (GnomeVFSMethod *method,
        return result;
 }
 
+static GnomeVFSResult
+do_seek (GnomeVFSMethod *method,
+        GnomeVFSMethodHandle *method_handle,
+        GnomeVFSSeekPosition  whence,
+        GnomeVFSFileOffset    offset,
+        GnomeVFSContext *context)
+{
+       HttpFileHandle *handle;
+
+       handle = (HttpFileHandle *) method_handle;
+
+       if (handle->to_be_written != NULL)
+               return GNOME_VFS_ERROR_NOT_SUPPORTED;
+
+       switch (whence) {
+       case GNOME_VFS_SEEK_START:
+               handle->offset = offset;
+               break;
+       case GNOME_VFS_SEEK_CURRENT:
+               handle->offset += offset;
+               break;
+       case GNOME_VFS_SEEK_END:
+               if (!(handle->file_info->flags & GNOME_VFS_FILE_INFO_FIELDS_SIZE))
+                       return GNOME_VFS_ERROR_NOT_SUPPORTED;
+               handle->offset = handle->file_info->size + offset;
+               break;
+       default:
+               g_return_val_if_reached(GNOME_VFS_ERROR_NOT_SUPPORTED);
+       }
+
+       return GNOME_VFS_OK;
+}
+
+static GnomeVFSResult
+do_tell (GnomeVFSMethod *method,
+        GnomeVFSMethodHandle *method_handle,
+        GnomeVFSFileOffset *offset_return)
+{
+       HttpFileHandle *handle;
+
+       handle = (HttpFileHandle *) method_handle;
+
+       *offset_return = handle->offset;
+
+       return GNOME_VFS_OK;
+}
+
 /* Directory handling - WebDAV servers only */
 
 static void
@@ -2210,17 +2364,19 @@ make_propfind_request (HttpFileHandle **handle_return,
                DEBUG_HTTP (("HTTP server returned an invalid PROPFIND response: %d", (*handle_return)->server_status));
                result = GNOME_VFS_ERROR_NOT_SUPPORTED;
        }
+       /* Some servers (download.microsoft.com) will just close
+        * the connection (EOF) without returning any HTTP status.
+        */
+       if (result == GNOME_VFS_ERROR_EOF) {
+               DEBUG_HTTP (("HTTP server returned an empty PROPFIND response"));
+               result = GNOME_VFS_ERROR_NOT_SUPPORTED;
+       }
        
        if (result == GNOME_VFS_OK) {
                do {
-                       GnomeVFSHttpMethodHandle *http_method_handle;
-
-                       http_method_handle = g_new (GnomeVFSHttpMethodHandle, 1);
-                       http_method_handle->http_file_handle = *handle_return;
-                       result = do_read (NULL, (GnomeVFSMethodHandle *) http_method_handle,
+                       result = do_read (NULL, (GnomeVFSMethodHandle *) *handle_return,
                                          buffer, num_bytes, &bytes_read, context);
-                       g_free (http_method_handle);
-
+                       
                        if (result != GNOME_VFS_OK ) {
                                break;
                        }
@@ -2275,7 +2431,7 @@ make_propfind_request (HttpFileHandle **handle_return,
                                (*handle_return)->files = g_list_append ((*handle_return)->files, file_info);
                        } else {
                                /* This response refers to the root node */
-                               /* Abandon the old information that came from create_handle*/
+                               /* Abandon the old information that came from init_handle */
                                
                                file_info->name = (*handle_return)->file_info->name;
                                (*handle_return)->file_info->name = NULL;
@@ -2336,7 +2492,6 @@ do_open_directory(GnomeVFSMethod *method,
        HttpFileHandle *handle = NULL;
        GnomeVFSFileInfo * file_info_cached;
        GList *child_file_info_cached_list = NULL;
-       GnomeVFSHttpMethodHandle *http_method_handle;
 
        ANALYZE_HTTP ("==> +do_open_directory");
        DEBUG_HTTP (("+Open_Directory options: %d URI: '%s'", options, gnome_vfs_uri_to_string (uri, 0)));
@@ -2390,11 +2545,8 @@ do_open_directory(GnomeVFSMethod *method,
                        handle = NULL;
                }
        }
-
-       http_method_handle = g_new (GnomeVFSHttpMethodHandle, 1);
-       http_method_handle->http_file_handle = handle;
-
-       *method_handle = (GnomeVFSMethodHandle *)http_method_handle;
+       
+       *method_handle = (GnomeVFSMethodHandle *)handle;
 
 error:
        DEBUG_HTTP (("-Open_Directory (%d) handle:0x%08x", result, (unsigned int)handle));
@@ -2409,14 +2561,11 @@ do_close_directory (GnomeVFSMethod *method,
                    GnomeVFSContext *context) 
 {
        HttpFileHandle *handle;
-       GnomeVFSHttpMethodHandle *http_method_handle;
        
        DEBUG_HTTP (("+Close_Directory"));
-
-       http_method_handle = (GnomeVFSHttpMethodHandle *) method_handle;
-       handle = http_method_handle->http_file_handle;
-       g_free (http_method_handle);
-
+       
+       handle = (HttpFileHandle *) method_handle;
+       
        http_handle_close(handle, context);
 
        DEBUG_HTTP (("-Close_Directory (0) handle:0x%08x", (unsigned int) method_handle));
@@ -2435,7 +2584,7 @@ do_read_directory (GnomeVFSMethod *method,
 
        DEBUG_HTTP (("+Read_Directory handle:0x%08x", (unsigned int) method_handle));
 
-       handle = ((GnomeVFSHttpMethodHandle *) method_handle)->http_file_handle;
+       handle = (HttpFileHandle *) method_handle;
        
        if (handle->files && g_list_length (handle->files)) {
                GnomeVFSFileInfo *original_info = g_list_nth_data (handle->files, 0);
@@ -2520,6 +2669,7 @@ do_get_file_info (GnomeVFSMethod *method,
                                        gnome_vfs_file_info_copy (file_info, handle->file_info);
                                        http_cache_add_uri (uri, handle->file_info, FALSE);
                                        http_handle_close (handle, context);
+                                       handle = NULL;
                                }
 
                                /* If we get a redirect, we should be
@@ -2573,8 +2723,8 @@ do_get_file_info_from_handle (GnomeVFSMethod *method,
        
        DEBUG_HTTP (("+Get_File_Info_From_Handle"));
        
-       handle = ((GnomeVFSHttpMethodHandle *) method_handle)->http_file_handle;
-
+       handle = (HttpFileHandle *) method_handle;
+       
        gnome_vfs_file_info_copy (file_info, handle->file_info);
        
        DEBUG_HTTP (("-Get_File_Info_From_Handle"));
@@ -2791,8 +2941,8 @@ static GnomeVFSMethod method = {
        do_close,
        do_read,
        do_write,
-       NULL, /* seek */
-       NULL, /* tell */
+       do_seek,
+       do_tell,
        NULL, /* truncate_handle */
        do_open_directory,
        do_close_directory,