diff options
Diffstat (limited to 'daemon/gvfsbackendftp.c')
-rw-r--r-- | daemon/gvfsbackendftp.c | 2410 |
1 files changed, 0 insertions, 2410 deletions
diff --git a/daemon/gvfsbackendftp.c b/daemon/gvfsbackendftp.c deleted file mode 100644 index b75e01ef..00000000 --- a/daemon/gvfsbackendftp.c +++ /dev/null @@ -1,2410 +0,0 @@ -/* GIO - GLib Input, Output and Streaming Library - * - * Copyright (C) 2008 Benjamin Otte <otte@gnome.org> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - * Author: Benjmain Otte <otte@gnome.org> - */ - - -#include <config.h> - -#include <errno.h> /* for strerror (EAGAIN) */ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <glib/gi18n.h> -#include <libsoup/soup.h> - -#include "gvfsbackendftp.h" -#include "gvfsjobopenforread.h" -#include "gvfsjobread.h" -#include "gvfsjobseekread.h" -#include "gvfsjobopenforwrite.h" -#include "gvfsjobwrite.h" -#include "gvfsjobseekwrite.h" -#include "gvfsjobsetdisplayname.h" -#include "gvfsjobqueryinfo.h" -#include "gvfsjobqueryfsinfo.h" -#include "gvfsjobqueryattributes.h" -#include "gvfsjobenumerate.h" -#include "gvfsdaemonprotocol.h" -#include "gvfsdaemonutils.h" -#include "gvfskeyring.h" - -#include "ParseFTPList.h" - -#define PRINT_DEBUG - -#ifdef PRINT_DEBUG -#define DEBUG g_print -#else -#define DEBUG(...) -#endif - -/* timeout for network connect/send/receive (use 0 for none) */ -#define TIMEOUT_IN_SECONDS 30 - -/* - * about filename interpretation in the ftp backend - * - * As GVfs composes paths using a slash character, we cannot allow a slash as - * part of a basename. Other critical characters are \r \n and sometimes the - * space. We therefore g_uri_escape_string() filenames by default and concatenate - * paths using slashes. This should make GVfs happy. - * - * Luckily, TVFS (see RFC 3xxx for details) is a specification that does exactly - * what we want. It disallows slashes, \r and \n in filenames, so we can happily - * use it without the need to escape. We also can operate on full paths as our - * paths exactly match those of a TVFS-using FTP server. - */ - -/* unsinged char is on purpose, so we get warnings when we misuse them */ -typedef unsigned char FtpFile; -typedef struct _FtpConnection FtpConnection; - -typedef struct FtpDirReader FtpDirReader; -struct FtpDirReader { - void (* init_data) (FtpConnection *conn, - const FtpFile *dir); - GFileInfo * (* get_root) (FtpConnection *conn); - gpointer (* iter_new) (FtpConnection *conn); - GFileInfo * (* iter_process)(gpointer iter, - FtpConnection *conn, - const FtpFile *dirname, - const FtpFile *must_match_file, - const char *line, - char **symlink); - void (* iter_free) (gpointer iter); -}; - -typedef enum { - FTP_FEATURE_MDTM = (1 << 0), - FTP_FEATURE_SIZE = (1 << 1), - FTP_FEATURE_TVFS = (1 << 2), - FTP_FEATURE_EPSV = (1 << 3) -} FtpFeatures; -#define FTP_FEATURES_DEFAULT (FTP_FEATURE_EPSV) - -typedef enum { - FTP_SYSTEM_UNKNOWN = 0, - FTP_SYSTEM_UNIX, - FTP_SYSTEM_WINDOWS -} FtpSystem; - -struct _GVfsBackendFtp -{ - GVfsBackend backend; - - SoupAddress * addr; - char * user; - gboolean has_initial_user; - char * password; /* password or NULL for anonymous */ - - /* vfuncs */ - const FtpDirReader * dir_ops; - - /* connection collection */ - GQueue * queue; - GMutex * mutex; - GCond * cond; - guint connections; - guint max_connections; - - /* caching results from dir queries */ - GStaticRWLock directory_cache_lock; - GHashTable * directory_cache; -}; - -G_DEFINE_TYPE (GVfsBackendFtp, g_vfs_backend_ftp, G_VFS_TYPE_BACKEND) - -#define STATUS_GROUP(status) ((status) / 100) - -typedef void (* Ftp550Handler) (FtpConnection *conn, const FtpFile *file); - -/*** FTP CONNECTION ***/ - -struct _FtpConnection -{ - /* per-job data */ - GError * error; - GVfsJob * job; - - FtpFeatures features; - FtpSystem system; - - SoupSocket * commands; - gchar * read_buffer; - gsize read_buffer_size; - gsize read_bytes; - - SoupSocket * data; -}; - -static void -ftp_connection_free (FtpConnection *conn) -{ - g_assert (conn->job == NULL); - - if (conn->commands) - g_object_unref (conn->commands); - if (conn->data) - g_object_unref (conn->data); - - g_slice_free (FtpConnection, conn); -} - -#define ftp_connection_in_error(conn) ((conn)->error != NULL) - -static gboolean -ftp_connection_pop_job (FtpConnection *conn) -{ - gboolean result; - GError *error; - GVfsJob *job; - - g_return_val_if_fail (conn->job != NULL, FALSE); - - /* sending a reply is racy after the reply is sent. The connection may be - * reused in a different thread before the reply sending returns. This is - * racy in particular when the connection is used as a read/write handle. - */ - error = conn->error; - conn->error = NULL; - job = conn->job; - conn->job = NULL; - - if (error) - { - g_vfs_job_failed_from_error (job, error); - g_clear_error (&error); - result = FALSE; - } - else - { - g_vfs_job_succeeded (job); - result = TRUE; - } - - return result; -} - -static void -ftp_connection_push_job (FtpConnection *conn, GVfsJob *job) -{ - g_return_if_fail (conn->job == NULL); - - /* FIXME: ref the job? */ - conn->job = job; -} - -/** - * ftp_error_set_from_response: - * @error: pointer to an error to be set or %NULL - * @response: an FTP response code to use as the error message - * - * Sets an error based on an FTP response code. - **/ -static void -ftp_connection_set_error_from_response (FtpConnection *conn, guint response) -{ - const char *msg; - int code; - - /* Please keep this list ordered by response code, - * but group responses with the same message. */ - switch (response) - { - case 332: /* Need account for login. */ - case 532: /* Need account for storing files. */ - /* FIXME: implement a sane way to handle accounts. */ - code = G_IO_ERROR_NOT_SUPPORTED; - msg = _("Accounts are unsupported"); - break; - case 421: /* Service not available, closing control connection. */ - code = G_IO_ERROR_FAILED; - msg = _("Host closed connection"); - break; - case 425: /* Can't open data connection. */ - code = G_IO_ERROR_CLOSED; - msg = _("Cannot open data connection. Maybe your firewall prevents this?"); - break; - case 426: /* Connection closed; transfer aborted. */ - code = G_IO_ERROR_CLOSED; - msg = _("Data connection closed"); - break; - case 450: /* Requested file action not taken. File unavailable (e.g., file busy). */ - case 550: /* Requested action not taken. File unavailable (e.g., file not found, no access). */ - /* FIXME: This is a lot of different errors. So we have to pretend to - * be smart here. */ - code = G_IO_ERROR_FAILED; - msg = _("Operation failed"); - break; - case 451: /* Requested action aborted: local error in processing. */ - code = G_IO_ERROR_FAILED; - msg = _("Operation failed"); - break; - case 452: /* Requested action not taken. Insufficient storage space in system. */ - case 552: - code = G_IO_ERROR_NO_SPACE; - msg = _("No space left on server"); - break; - case 500: /* Syntax error, command unrecognized. */ - case 501: /* Syntax error in parameters or arguments. */ - case 502: /* Command not implemented. */ - case 503: /* Bad sequence of commands. */ - case 504: /* Command not implemented for that parameter. */ - code = G_IO_ERROR_NOT_SUPPORTED; - msg = _("Operation unsupported"); - break; - case 530: /* Not logged in. */ - code = G_IO_ERROR_PERMISSION_DENIED; - msg = _("Permission denied"); - break; - case 551: /* Requested action aborted: page type unknown. */ - code = G_IO_ERROR_FAILED; - msg = _("Page type unknown"); - break; - case 553: /* Requested action not taken. File name not allowed. */ - code = G_IO_ERROR_INVALID_FILENAME; - msg = _("Invalid filename"); - break; - default: - code = G_IO_ERROR_FAILED; - msg = _("Invalid reply"); - break; - } - - DEBUG ("error: %s\n", msg); - g_set_error_literal (&conn->error, G_IO_ERROR, code, msg); -} - -/** - * ResponseFlags: - * RESPONSE_PASS_100: Don't treat 1XX responses, but return them - * RESPONSE_PASS_300: Don't treat 3XX responses, but return them - * RESPONSE_PASS_400: Don't treat 4XX responses, but return them - * RESPONSE_PASS_500: Don't treat 5XX responses, but return them - * RESPONSE_PASS_550: Don't treat 550 responses, but return them - * RESPONSE_FAIL_200: Fail on a 2XX response - */ - -typedef enum { - RESPONSE_PASS_100 = (1 << 0), - RESPONSE_PASS_300 = (1 << 1), - RESPONSE_PASS_400 = (1 << 2), - RESPONSE_PASS_500 = (1 << 3), - RESPONSE_PASS_550 = (1 << 4), - RESPONSE_FAIL_200 = (1 << 5) -} ResponseFlags; - -/** - * ftp_connection_receive: - * @conn: connection to receive from - * @flags: flags for handling the response - * @error: pointer to error message - * - * Reads a command and stores it in @conn->read_buffer. The read buffer will be - * null-terminated and contain @conn->read_bytes bytes. Afterwards, the response - * will be parsed and processed according to @flags. By default, all responses - * but 2xx will cause an error. - * - * Returns: 0 on error, the ftp code otherwise - **/ -static guint -ftp_connection_receive (FtpConnection *conn, - ResponseFlags flags) -{ - SoupSocketIOStatus status; - gsize n_bytes; - gboolean got_boundary; - char *last_line; - enum { - FIRST_LINE, - MULTILINE, - DONE - } reply_state = FIRST_LINE; - guint response = 0; - - g_assert (conn->job != NULL); - - if (ftp_connection_in_error (conn)) - return 0; - - conn->read_bytes = 0; - while (reply_state != DONE) - { - if (conn->read_buffer_size - conn->read_bytes < 128) - { - gsize new_size = conn->read_buffer_size + 1024; - /* FIXME: upper limit for size? */ - gchar *new = g_try_realloc (conn->read_buffer, new_size); - if (new) - { - conn->read_buffer = new; - conn->read_buffer_size = new_size; - } - else - { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Invalid reply")); - return 0; - } - } - last_line = conn->read_buffer + conn->read_bytes; - status = soup_socket_read_until (conn->commands, - last_line, - /* -1 byte for nul-termination */ - conn->read_buffer_size - conn->read_bytes - 1, - "\r\n", - 2, - &n_bytes, - &got_boundary, - conn->job->cancellable, - &conn->error); - - conn->read_bytes += n_bytes; - conn->read_buffer[conn->read_bytes] = 0; - DEBUG ("<-- %s", last_line); - - switch (status) - { - case SOUP_SOCKET_OK: - case SOUP_SOCKET_EOF: - if (got_boundary) - break; - if (n_bytes > 0) - continue; - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Invalid reply")); - /* fall through */ - case SOUP_SOCKET_ERROR: - conn->read_buffer[conn->read_bytes] = 0; - return 0; - case SOUP_SOCKET_WOULD_BLOCK: - default: - g_assert_not_reached (); - break; - } - - if (reply_state == FIRST_LINE) - { - if (n_bytes < 4 || - last_line[0] <= '0' || last_line[0] > '5' || - last_line[1] < '0' || last_line[1] > '9' || - last_line[2] < '0' || last_line[2] > '9') - { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Invalid reply")); - return 0; - } - response = 100 * (last_line[0] - '0') + - 10 * (last_line[1] - '0') + - (last_line[2] - '0'); - if (last_line[3] == ' ') - reply_state = DONE; - else if (last_line[3] == '-') - reply_state = MULTILINE; - else - { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Invalid reply")); - return 0; - } - } - else - { - if (n_bytes >= 4 && - memcmp (conn->read_buffer, last_line, 3) == 0 && - last_line[3] == ' ') - reply_state = DONE; - } - } - - switch (STATUS_GROUP (response)) - { - case 0: - return 0; - case 1: - if (flags & RESPONSE_PASS_100) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - case 2: - if (flags & RESPONSE_FAIL_200) - { - ftp_connection_set_error_from_response (conn, response); - return 0; - } - break; - case 3: - if (flags & RESPONSE_PASS_300) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - case 4: - if (flags & RESPONSE_PASS_400) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - break; - case 5: - if ((flags & RESPONSE_PASS_500) || (response == 550 && (flags & RESPONSE_PASS_550))) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - default: - g_assert_not_reached (); - break; - } - - return response; -} - -/** - * ftp_connection_send: - * @conn: the connection to send to - * @flags: #ResponseFlags to use - * @error: pointer to take an error - * @format: format string to construct command from - * (without trailing \r\n) - * @...: arguments to format string - * - * Takes a command, waits for an answer and parses it. Without any @flags, FTP - * codes other than 2xx cause an error. The last read ftp command will be put - * into @conn->read_buffer. - * - * Returns: 0 on error or the receied FTP code otherwise. - * - **/ -static guint -ftp_connection_sendv (FtpConnection *conn, - ResponseFlags flags, - const char * format, - va_list varargs) -{ - GString *command; - SoupSocketIOStatus status; - gsize n_bytes; - guint response; - - g_assert (conn->job != NULL); - - if (ftp_connection_in_error (conn)) - return 0; - - command = g_string_new (""); - g_string_append_vprintf (command, format, varargs); -#ifdef PRINT_DEBUG - if (g_str_has_prefix (command->str, "PASS")) - DEBUG ("--> PASS ***\n"); - else - DEBUG ("--> %s\n", command->str); -#endif - g_string_append (command, "\r\n"); - status = soup_socket_write (conn->commands, - command->str, - command->len, - &n_bytes, - conn->job->cancellable, - &conn->error); - switch (status) - { - case SOUP_SOCKET_OK: - case SOUP_SOCKET_EOF: - if (n_bytes == command->len) - break; - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("broken transmission")); - /* fall through */ - case SOUP_SOCKET_ERROR: - g_string_free (command, TRUE); - return 0; - case SOUP_SOCKET_WOULD_BLOCK: - default: - g_assert_not_reached (); - } - g_string_free (command, TRUE); - - response = ftp_connection_receive (conn, flags); - return response; -} - -static guint -ftp_connection_send (FtpConnection *conn, - ResponseFlags flags, - const char * format, - ...) G_GNUC_PRINTF (3, 4); -static guint -ftp_connection_send (FtpConnection *conn, - ResponseFlags flags, - const char * format, - ...) -{ - va_list varargs; - guint response; - - va_start (varargs, format); - response = ftp_connection_sendv (conn, - flags, - format, - varargs); - va_end (varargs); - return response; -} - -static void -ftp_connection_check_file (FtpConnection *conn, - const Ftp550Handler *handlers, - const FtpFile *file) -{ - while (*handlers && !ftp_connection_in_error (conn)) - { - (*handlers) (conn, file); - handlers++; - } -} - -static guint -ftp_connection_send_and_check (FtpConnection *conn, - ResponseFlags flags, - const Ftp550Handler *handlers, - const FtpFile *file, - const char *format, - ...) G_GNUC_PRINTF (5, 6); -static guint -ftp_connection_send_and_check (FtpConnection *conn, - ResponseFlags flags, - const Ftp550Handler *handlers, - const FtpFile *file, - const char *format, - ...) -{ - va_list varargs; - guint response; - - /* check that there's no 550 handling used - don't allow bad use of API */ - g_return_val_if_fail ((flags & RESPONSE_PASS_550) == 0, 0); - g_return_val_if_fail (handlers != NULL, 0); - g_return_val_if_fail (file != NULL, 0); - - va_start (varargs, format); - response = ftp_connection_sendv (conn, - flags | RESPONSE_PASS_550, - format, - varargs); - va_end (varargs); - if (response == 550) - { - ftp_connection_check_file (conn, handlers, file); - if (!ftp_connection_in_error (conn)) - ftp_connection_set_error_from_response (conn, response); - response = 0; - } - return response; -} - -static void -ftp_connection_parse_features (FtpConnection *conn) -{ - struct { - const char * name; /* name of feature */ - FtpFeatures enable; /* flags to enable with this feature */ - } features[] = { - { "MDTM", FTP_FEATURE_MDTM }, - { "SIZE", FTP_FEATURE_SIZE }, - { "TVFS", FTP_FEATURE_TVFS }, - { "EPSV", FTP_FEATURE_EPSV } - }; - char **supported; - guint i, j; - - supported = g_strsplit (conn->read_buffer, "\r\n", -1); - - for (i = 1; supported[i]; i++) - { - const char *feature = supported[i]; - if (feature[0] != ' ') - continue; - feature++; - for (j = 0; j < G_N_ELEMENTS (features); j++) - { - if (g_ascii_strcasecmp (feature, features[j].name) == 0) - { - DEBUG ("feature %s supported\n", features[j].name); - conn->features |= features[j].enable; - } - } - } -} - -/* NB: you must free the connection if it's in error returning from here */ -static FtpConnection * -ftp_connection_create (SoupAddress * addr, - GVfsJob * job) -{ - FtpConnection *conn; - guint status; - - conn = g_slice_new0 (FtpConnection); - ftp_connection_push_job (conn, job); - - conn->commands = soup_socket_new ("non-blocking", FALSE, - "remote-address", addr, - "timeout", TIMEOUT_IN_SECONDS, - NULL); - status = soup_socket_connect_sync (conn->commands, job->cancellable); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) - { - /* FIXME: better error messages depending on status please */ - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_HOST_NOT_FOUND, - _("Could not connect to host")); - } - - ftp_connection_receive (conn, 0); - return conn; -} - -static guint -ftp_connection_login (FtpConnection *conn, - const char * username, - const char * password) -{ - guint status; - - if (ftp_connection_in_error (conn)) - return 0; - - status = ftp_connection_send (conn, RESPONSE_PASS_300, - "USER %s", username); - - if (STATUS_GROUP (status) == 3) - { - /* rationale for choosing the default password: - * - some ftp servers expect something that looks like an email address - * - we don't want to send the user's name or address, as that would be - * a privacy problem - * - we want to give ftp server administrators a chance to notify us of - * problems with our client. - * - we don't want to drown in spam. - */ - if (password == NULL) - password = "gvfsd-ftp-" VERSION "@example.com"; - status = ftp_connection_send (conn, 0, - "PASS %s", password); - } - - return status; -} - -static void -ftp_connection_parse_system (FtpConnection *conn) -{ - static const struct { - const char *id; - FtpSystem system; - } known_systems[] = { - /* NB: the first entry that matches is taken, so order matters */ - { "UNIX ", FTP_SYSTEM_UNIX }, - { "WINDOWS_NT ", FTP_SYSTEM_WINDOWS } - }; - guint i; - char *system_name = conn->read_buffer + 4; - - for (i = 0; i < G_N_ELEMENTS (known_systems); i++) - { - if (g_ascii_strncasecmp (system_name, - known_systems[i].id, - strlen (known_systems[i].id)) == 0) - { - conn->system = known_systems[i].system; - DEBUG ("system is %u\n", conn->system); - break; - } - } -} - -static gboolean -ftp_connection_use (FtpConnection *conn) -{ - /* only binary transfers please */ - ftp_connection_send (conn, 0, "TYPE I"); - if (ftp_connection_in_error (conn)) - return FALSE; - - /* check supported features */ - if (ftp_connection_send (conn, 0, "FEAT") != 0) - ftp_connection_parse_features (conn); - else - conn->features = FTP_FEATURES_DEFAULT; - - /* RFC 2428 suggests to send this to make NAT routers happy */ - if (conn->features & FTP_FEATURE_EPSV) - ftp_connection_send (conn, 0, "EPSV ALL"); - g_clear_error (&conn->error); - - if (ftp_connection_send (conn, 0, "SYST")) - ftp_connection_parse_system (conn); - g_clear_error (&conn->error); - - return TRUE; -} - -static gboolean -ftp_connection_ensure_data_connection (FtpConnection *conn) -{ - guint ip1, ip2, ip3, ip4, port1, port2; - SoupAddress *addr; - const char *s; - char *ip; - guint status; - - if (conn->features & FTP_FEATURE_EPSV) - { - status = ftp_connection_send (conn, RESPONSE_PASS_500, "EPSV"); - if (STATUS_GROUP (status) == 2) - { - s = strrchr (conn->read_buffer, '('); - if (s) - { - guint port; - s += 4; - port = strtoul (s, NULL, 10); - if (port != 0) - { - addr = soup_address_new ( - soup_address_get_name (soup_socket_get_remote_address (conn->commands)), - port); - goto have_address; - } - } - } - } - /* only binary transfers please */ - status = ftp_connection_send (conn, 0, "PASV"); - if (status == 0) - return FALSE; - - /* parse response and try to find the address to connect to. - * This code does the sameas curl. - */ - for (s = conn->read_buffer; *s; s++) - { - if (sscanf (s, "%u,%u,%u,%u,%u,%u", - &ip1, &ip2, &ip3, &ip4, - &port1, &port2) == 6) - break; - } - if (*s == 0) - { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Invalid reply")); - return FALSE; - } - ip = g_strdup_printf ("%u.%u.%u.%u", ip1, ip2, ip3, ip4); - addr = soup_address_new (ip, port1 << 8 | port2); - g_free (ip); - -have_address: - conn->data = soup_socket_new ("non-blocking", FALSE, - "remote-address", addr, - "timeout", TIMEOUT_IN_SECONDS, - NULL); - g_object_unref (addr); - status = soup_socket_connect_sync (conn->data, conn->job->cancellable); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) - { - /* FIXME: better error messages depending on status please */ - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_HOST_NOT_FOUND, - _("Could not connect to host")); - g_object_unref (conn->data); - conn->data = NULL; - return FALSE; - } - - return TRUE; -} - -static void -ftp_connection_close_data_connection (FtpConnection *conn) -{ - if (conn == NULL || conn->data == NULL) - return; - - g_object_unref (conn->data); - conn->data = NULL; -} - -/*** FILE MAPPINGS ***/ - -/* FIXME: This most likely needs adaption to non-unix like directory structures. - * There's at least the case of multiple roots (Netware) plus probably a shitload - * of weird old file systems (starting with MS-DOS) - * But we first need a way to detect that. - */ - -/** - * FtpFile: - * - * Byte string used to identify a file on the FTP server. It's typedef'ed to - * make it easy to distinguish from GVfs paths. - */ - -static FtpFile * -ftp_filename_from_gvfs_path (FtpConnection *conn, const char *pathname) -{ - return (FtpFile *) g_strdup (pathname); -} - -static char * -ftp_filename_to_gvfs_path (FtpConnection *conn, const FtpFile *filename) -{ - return g_strdup ((const char *) filename); -} - -/* Takes an FTP dirname and a basename (as used in RNTO or as result from LIST - * or similar) and gets the new ftp filename from it. - * - * Returns: the filename or %NULL if filename construction wasn't possible. - */ -/* let's hope we can live without a connection here, or we have to rewrite LIST */ -static FtpFile * -ftp_filename_construct (FtpConnection *conn, const FtpFile *dirname, const char *basename) -{ - if (strpbrk (basename, "/\r\n")) - return NULL; - - return (FtpFile *) g_build_path ("/", (char *) dirname, basename, NULL); -} - -#define ftp_filename_equal g_str_equal - -/*** COMMON FUNCTIONS WITH SPECIAL HANDLING ***/ - -static gboolean -ftp_connection_cd (FtpConnection *conn, const FtpFile *file) -{ - guint response = ftp_connection_send (conn, - RESPONSE_PASS_500, - "CWD %s", file); - if (response == 550) - { - g_set_error_literal (&conn->error, - G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, - _("The file is not a directory")); - response = 0; - } - else if (STATUS_GROUP (response) == 5) - { - ftp_connection_set_error_from_response (conn, response); - response = 0; - } - - return response != 0; -} - -static gboolean -ftp_connection_try_cd (FtpConnection *conn, const FtpFile *file) -{ - if (ftp_connection_in_error (conn)) - return FALSE; - - if (!ftp_connection_cd (conn, file)) - { - g_clear_error (&conn->error); - return FALSE; - } - - return TRUE; -} - -/*** default directory reading ***/ - -static void -dir_default_init_data (FtpConnection *conn, const FtpFile *dir) -{ - ftp_connection_cd (conn, dir); - ftp_connection_ensure_data_connection (conn); - - ftp_connection_send (conn, - RESPONSE_PASS_100 | RESPONSE_FAIL_200, - "LIST"); -} - -static GFileInfo * -dir_default_get_root (FtpConnection *conn) -{ - GFileInfo *info; - GIcon *icon; - char *display_name; - - info = g_file_info_new (); - g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); - - g_file_info_set_name (info, "/"); - display_name = g_strdup_printf (_("/ on %s"), - soup_address_get_name (soup_socket_get_remote_address (conn->commands))); - g_file_info_set_display_name (info, display_name); - g_free (display_name); - g_file_info_set_edit_name (info, "/"); - - g_file_info_set_content_type (info, "inode/directory"); - g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "inode/directory"); - - icon = g_themed_icon_new ("folder-remote"); - g_file_info_set_icon (info, icon); - g_object_unref (icon); - - return info; -} - -static gpointer -dir_default_iter_new (FtpConnection *conn) -{ - return g_slice_new (struct list_state); -} - -static GFileInfo * -dir_default_iter_process (gpointer iter, - FtpConnection *conn, - const FtpFile *dirname, - const FtpFile *must_match_file, - const char *line, - char **symlink) -{ - struct list_state *state = iter; - struct list_result result = { 0, }; - GTimeVal tv = { 0, 0 }; - GFileInfo *info; - int type; - FtpFile *name; - char *s, *t; - - type = ParseFTPList (line, state, &result); - if (type != 'd' && type != 'f' && type != 'l') - return NULL; - - /* don't list . and .. directories - * Let's hope they're not important files on some ftp servers - */ - if (type == 'd') - { - if (result.fe_fnlen == 1 && - result.fe_fname[0] == '.') - return NULL; - if (result.fe_fnlen == 2 && - result.fe_fname[0] == '.' && - result.fe_fname[1] == '.') - return NULL; - } - - s = g_strndup (result.fe_fname, result.fe_fnlen); - if (dirname) - { - name = ftp_filename_construct (conn, dirname, s); - g_free (s); - } - else - name = (FtpFile *) s; - if (name == NULL) - return NULL; - - if (must_match_file && !ftp_filename_equal (name, must_match_file)) - { - g_free (name); - return NULL; - } - - info = g_file_info_new (); - - s = ftp_filename_to_gvfs_path (conn, name); - - t = g_path_get_basename (s); - g_file_info_set_name (info, t); - g_free (t); - - if (type == 'l') - { - char *link; - - link = g_strndup (result.fe_lname, result.fe_lnlen); - - /* FIXME: this whole stuff is not FtpFile save */ - g_file_info_set_symlink_target (info, link); - g_file_info_set_is_symlink (info, TRUE); - - if (symlink) - { - char *str = g_path_get_dirname (s); - char *symlink_file = g_build_path ("/", str, link, NULL); - - g_free (str); - while ((str = strstr (symlink_file, "/../"))) - { - char *end = str + 4; - char *start; - start = str - 1; - while (*start != '/') - start--; - memcpy (start + 1, end, strlen (end) + 1); - } - str = symlink_file + strlen (symlink_file) - 1; - while (*str == '/' && str > symlink_file) - *str-- = 0; - *symlink = symlink_file; - } - g_free (link); - } - else if (symlink) - *symlink = NULL; - - g_file_info_set_size (info, strtoul (result.fe_size, NULL, 10)); - - gvfs_file_info_populate_default (info, s, - type == 'f' ? G_FILE_TYPE_REGULAR : - type == 'l' ? G_FILE_TYPE_SYMBOLIC_LINK : - G_FILE_TYPE_DIRECTORY); - - if (conn->system == FTP_SYSTEM_UNIX) - g_file_info_set_is_hidden (info, result.fe_fnlen > 0 && - result.fe_fname[0] == '.'); - - g_free (s); - g_free (name); - - tv.tv_sec = mktime (&result.fe_time); - if (tv.tv_sec != -1) - g_file_info_set_modification_time (info, &tv); - - return info; -} - -static void -dir_default_iter_free (gpointer iter) -{ - g_slice_free (struct list_state, iter); -} - -static const FtpDirReader dir_default = { - dir_default_init_data, - dir_default_get_root, - dir_default_iter_new, - dir_default_iter_process, - dir_default_iter_free -}; - -/*** BACKEND ***/ - -static void -g_vfs_backend_ftp_push_connection (GVfsBackendFtp *ftp, FtpConnection *conn) -{ - /* we allow conn == NULL to ease error cases */ - if (conn == NULL) - return; - - if (conn->job) - ftp_connection_pop_job (conn); - - g_mutex_lock (ftp->mutex); - if (ftp->queue) - { - g_queue_push_tail (ftp->queue, conn); - g_cond_signal (ftp->cond); - } - else - ftp_connection_free (conn); - g_mutex_unlock (ftp->mutex); -} - -static void -do_broadcast (GCancellable *cancellable, GCond *cond) -{ - g_cond_broadcast (cond); -} - -static FtpConnection * -g_vfs_backend_ftp_pop_connection (GVfsBackendFtp *ftp, - GVfsJob * job) -{ - FtpConnection *conn = NULL; - GTimeVal now; - guint id; - - g_mutex_lock (ftp->mutex); - id = g_signal_connect (job->cancellable, - "cancelled", - G_CALLBACK (do_broadcast), - ftp->cond); - while (conn == NULL && ftp->queue != NULL) - { - if (g_cancellable_is_cancelled (job->cancellable)) - break; - conn = g_queue_pop_head (ftp->queue); - - if (conn != NULL) { - /* Figure out if this connection had a timeout sent. If so, skip it. */ - g_mutex_unlock (ftp->mutex); - ftp_connection_push_job (conn, job); - if (ftp_connection_send (conn, 0, "NOOP")) - break; - - g_clear_error (&conn->error); - conn->job = NULL; - ftp_connection_free (conn); - conn = NULL; - g_mutex_lock (ftp->mutex); - ftp->connections--; - continue; - } - - if (ftp->connections < ftp->max_connections) - { - ftp->connections++; - g_mutex_unlock (ftp->mutex); - conn = ftp_connection_create (ftp->addr, job); - ftp_connection_login (conn, ftp->user, ftp->password); - ftp_connection_use (conn); - if (!ftp_connection_in_error (conn)) - break; - - ftp_connection_pop_job (conn); - ftp_connection_free (conn); - conn = NULL; - g_mutex_lock (ftp->mutex); - ftp->connections--; - /* FIXME: This assignment is racy due to the mutex unlock above */ - ftp->max_connections = ftp->connections; - if (ftp->max_connections == 0) - { - DEBUG ("no more connections left, exiting..."); - /* FIXME: shut down properly */ - exit (0); - } - - continue; - } - - g_get_current_time (&now); - g_time_val_add (&now, TIMEOUT_IN_SECONDS * 1000 * 1000); - if (!g_cond_timed_wait (ftp->cond, ftp->mutex, &now)) - { - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, - /* defeat string freeze! */ - /* _("Resource temporarily unavailable")); */ - "%s", g_strerror (EAGAIN)); - break; - } - } - g_signal_handler_disconnect (job->cancellable, id); - - return conn; -} - -static void -g_vfs_backend_ftp_finalize (GObject *object) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (object); - - if (ftp->addr) - g_object_unref (ftp->addr); - - /* has been cleared on unmount */ - g_assert (ftp->queue == NULL); - g_cond_free (ftp->cond); - g_mutex_free (ftp->mutex); - - g_hash_table_destroy (ftp->directory_cache); - g_static_rw_lock_free (&ftp->directory_cache_lock); - - g_free (ftp->user); - g_free (ftp->password); - - if (G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize) - (*G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize) (object); -} - -static void -list_free (gpointer list) -{ - g_list_foreach (list, (GFunc) g_free, NULL); - g_list_free (list); -} - -static void -g_vfs_backend_ftp_init (GVfsBackendFtp *ftp) -{ - ftp->mutex = g_mutex_new (); - ftp->cond = g_cond_new (); - - ftp->directory_cache = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - list_free); - g_static_rw_lock_init (&ftp->directory_cache_lock); - - ftp->dir_ops = &dir_default; -} - -static void -do_mount (GVfsBackend *backend, - GVfsJobMount *job, - GMountSpec *mount_spec, - GMountSource *mount_source, - gboolean is_automount) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - char *host; - char *prompt = NULL; - char *username; - char *password; - char *display_name; - gboolean aborted, anonymous, break_on_fail; - GPasswordSave password_save = G_PASSWORD_SAVE_NEVER; - guint port; - - conn = ftp_connection_create (ftp->addr, - G_VFS_JOB (job)); - /* fail fast here. No need to ask for a password if we know the hostname - * doesn't exist or the given host/port doesn't have an ftp server running. - */ - if (ftp_connection_in_error (conn)) - { - ftp_connection_pop_job (conn); - ftp_connection_free (conn); - return; - } - - port = soup_address_get_port (ftp->addr); - /* FIXME: need to translate this? */ - if (port == 21) - host = g_strdup (soup_address_get_name (ftp->addr)); - else - host = g_strdup_printf ("%s:%u", - soup_address_get_name (ftp->addr), - port); - - username = NULL; - password = NULL; - break_on_fail = FALSE; - - if (ftp->user != NULL && strcmp (ftp->user, "anonymous") == 0) - { - anonymous = TRUE; - break_on_fail = TRUE; - goto try_login; - } - - if (g_vfs_keyring_lookup_password (ftp->user, - soup_address_get_name (ftp->addr), - NULL, - "ftp", - NULL, - NULL, - port == 21 ? 0 : port, - &username, - NULL, - &password)) - { - anonymous = FALSE; - goto try_login; - } - - while (TRUE) - { - GAskPasswordFlags flags; - if (prompt == NULL) - /* translators: %s here is the hostname */ - prompt = g_strdup_printf (_("Enter password for ftp on %s"), host); - - flags = G_ASK_PASSWORD_NEED_PASSWORD; - - if (!ftp->has_initial_user) - flags |= G_ASK_PASSWORD_NEED_USERNAME | G_ASK_PASSWORD_ANONYMOUS_SUPPORTED; - - if (g_vfs_keyring_is_available ()) - flags |= G_ASK_PASSWORD_SAVING_SUPPORTED; - - if (!g_mount_source_ask_password ( - mount_source, - prompt, - ftp->user, - NULL, - flags, - &aborted, - &password, - &username, - NULL, - &anonymous, - &password_save) || - aborted) - { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, - _("Password dialog cancelled")); - break; - } - - /* NEED_USERNAME wasn't set */ - if (ftp->has_initial_user) - { - g_free (username); - username = g_strdup (ftp->user); - } - -try_login: - g_free (ftp->user); - g_free (ftp->password); - if (anonymous) - { - if (ftp_connection_login (conn, "anonymous", "") != 0) - { - ftp->user = g_strdup ("anonymous"); - ftp->password = g_strdup (""); - break; - } - ftp->user = NULL; - ftp->password = NULL; - } - else - { - ftp->user = username ? g_strdup (username) : g_strdup (""); - ftp->password = g_strdup (password); - if (ftp_connection_login (conn, username, password) != 0) - break; - } - g_free (username); - g_free (password); - - if (break_on_fail || - !g_error_matches (conn->error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) - break; - - g_clear_error (&conn->error); - } - - ftp_connection_use (conn); - - if (ftp_connection_in_error (conn)) - { - ftp_connection_pop_job (conn); - ftp_connection_free (conn); - } - else - { - if (prompt && !anonymous) - { - /* a prompt was created, so we have to save the password */ - g_vfs_keyring_save_password (ftp->user, - soup_address_get_name (ftp->addr), - NULL, - "ftp", - NULL, - NULL, - port == 21 ? 0 : port, - ftp->password, - password_save); - g_free (prompt); - } - - mount_spec = g_mount_spec_new ("ftp"); - g_mount_spec_set (mount_spec, "host", soup_address_get_name (ftp->addr)); - if (port != 21) - { - char *port_str = g_strdup_printf ("%u", port); - g_mount_spec_set (mount_spec, "port", port_str); - g_free (port_str); - } - - if (ftp->has_initial_user) - g_mount_spec_set (mount_spec, "user", ftp->user); - - if (g_str_equal (ftp->user, "anonymous")) - display_name = g_strdup_printf (_("ftp on %s"), host); - else - { - /* Translators: the first %s is the username, the second the host name */ - display_name = g_strdup_printf (_("ftp as %s on %s"), ftp->user, host); - } - g_vfs_backend_set_mount_spec (backend, mount_spec); - g_mount_spec_unref (mount_spec); - - g_vfs_backend_set_display_name (backend, display_name); - g_free (display_name); - g_vfs_backend_set_icon_name (backend, "folder-remote"); - - ftp->connections = 1; - ftp->max_connections = G_MAXUINT; - ftp->queue = g_queue_new (); - g_vfs_backend_ftp_push_connection (ftp, conn); - } - - g_free (host); -} - -static gboolean -try_mount (GVfsBackend *backend, - GVfsJobMount *job, - GMountSpec *mount_spec, - GMountSource *mount_source, - gboolean is_automount) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - const char *host, *port_str; - guint port; - - host = g_mount_spec_get (mount_spec, "host"); - if (host == NULL) - { - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - _("No hostname specified")); - return TRUE; - } - port_str = g_mount_spec_get (mount_spec, "port"); - if (port_str == NULL) - port = 21; - else - { - /* FIXME: error handling? */ - port = strtoul (port_str, NULL, 10); - } - - ftp->addr = soup_address_new (host, port); - ftp->user = g_strdup (g_mount_spec_get (mount_spec, "user")); - ftp->has_initial_user = ftp->user != NULL; - - return FALSE; -} - -static void -do_unmount (GVfsBackend * backend, - GVfsJobUnmount *job) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - - g_mutex_lock (ftp->mutex); - while ((conn = g_queue_pop_head (ftp->queue))) - { - /* FIXME: properly quit */ - ftp_connection_free (conn); - } - g_queue_free (ftp->queue); - ftp->queue = NULL; - g_cond_broadcast (ftp->cond); - g_mutex_unlock (ftp->mutex); - g_vfs_job_succeeded (G_VFS_JOB (job)); -} - -static void -error_550_is_directory (FtpConnection *conn, const FtpFile *file) -{ - guint response = ftp_connection_send (conn, - RESPONSE_PASS_550, - "CWD %s", file); - - if (STATUS_GROUP (response) == 2) - { - g_set_error_literal (&conn->error, G_IO_ERROR, - G_IO_ERROR_IS_DIRECTORY, - _("File is directory")); - } -} - -static void -do_open_for_read (GVfsBackend *backend, - GVfsJobOpenForRead *job, - const char *filename) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - FtpFile *file; - static const Ftp550Handler open_read_handlers[] = { error_550_is_directory, NULL }; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (!conn) - return; - - ftp_connection_ensure_data_connection (conn); - - file = ftp_filename_from_gvfs_path (conn, filename); - ftp_connection_send_and_check (conn, - RESPONSE_PASS_100 | RESPONSE_FAIL_200, - &open_read_handlers[0], - file, - "RETR %s", file); - g_free (file); - - if (ftp_connection_in_error (conn)) - g_vfs_backend_ftp_push_connection (ftp, conn); - else - { - /* don't push the connection back, it's our handle now */ - g_vfs_job_open_for_read_set_handle (job, conn); - g_vfs_job_open_for_read_set_can_seek (job, FALSE); - ftp_connection_pop_job (conn); - } -} - -static void -do_close_read (GVfsBackend * backend, - GVfsJobCloseRead *job, - GVfsBackendHandle handle) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn = handle; - - ftp_connection_push_job (conn, G_VFS_JOB (job)); - ftp_connection_close_data_connection (conn); - ftp_connection_receive (conn, 0); - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -do_read (GVfsBackend * backend, - GVfsJobRead * job, - GVfsBackendHandle handle, - char * buffer, - gsize bytes_requested) -{ - FtpConnection *conn = handle; - gsize n_bytes; - - ftp_connection_push_job (conn, G_VFS_JOB (job)); - - soup_socket_read (conn->data, - buffer, - bytes_requested, - &n_bytes, - conn->job->cancellable, - &conn->error); - /* no need to check return value, code will just do the right thing - * depenging on wether conn->error is set */ - - g_vfs_job_read_set_size (job, n_bytes); - ftp_connection_pop_job (conn); -} - -static void -do_start_write (GVfsBackendFtp *ftp, - FtpConnection *conn, - GFileCreateFlags flags, - const char *format, - ...) G_GNUC_PRINTF (4, 5); -static void -do_start_write (GVfsBackendFtp *ftp, - FtpConnection *conn, - GFileCreateFlags flags, - const char *format, - ...) -{ - va_list varargs; - guint status; - - /* FIXME: can we honour the flags? */ - - ftp_connection_ensure_data_connection (conn); - - va_start (varargs, format); - status = ftp_connection_sendv (conn, - RESPONSE_PASS_100 | RESPONSE_FAIL_200, - format, - varargs); - va_end (varargs); - - if (ftp_connection_in_error (conn)) - g_vfs_backend_ftp_push_connection (ftp, conn); - else - { - /* don't push the connection back, it's our handle now */ - g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (conn->job), conn); - g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (conn->job), FALSE); - ftp_connection_pop_job (conn); - } -} - -static void -gvfs_backend_ftp_purge_cache_directory (GVfsBackendFtp *ftp, - const FtpFile * dir) -{ - g_static_rw_lock_writer_lock (&ftp->directory_cache_lock); - g_hash_table_remove (ftp->directory_cache, dir); - g_static_rw_lock_writer_unlock (&ftp->directory_cache_lock); -} - -static void -gvfs_backend_ftp_purge_cache_of_file (GVfsBackendFtp *ftp, - FtpConnection * conn, - const FtpFile * file) -{ - char *dirname, *filename; - FtpFile *dir; - - filename = ftp_filename_to_gvfs_path (conn, file); - dirname = g_path_get_dirname (filename); - dir = ftp_filename_from_gvfs_path (conn, dirname); - - gvfs_backend_ftp_purge_cache_directory (ftp, dir); - - g_free (dir); - g_free (filename); - g_free (dirname); -} - -/* forward declaration */ -static GFileInfo * -create_file_info (GVfsBackendFtp *ftp, FtpConnection *conn, const char *filename, char **symlink); - -static void -do_create (GVfsBackend *backend, - GVfsJobOpenForWrite *job, - const char *filename, - GFileCreateFlags flags) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - GFileInfo *info; - FtpFile *file; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - info = create_file_info (ftp, conn, filename, NULL); - if (info) - { - g_object_unref (info); - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_EXISTS, - _("Target file already exists")); - goto error; - } - file = ftp_filename_from_gvfs_path (conn, filename); - do_start_write (ftp, conn, flags, "STOR %s", file); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); - g_free (file); - return; - -error: - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -do_append (GVfsBackend *backend, - GVfsJobOpenForWrite *job, - const char *filename, - GFileCreateFlags flags) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - FtpFile *file; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - file = ftp_filename_from_gvfs_path (conn, filename); - do_start_write (ftp, conn, flags, "APPE %s", filename); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); - g_free (file); - return; -} - -static void -do_replace (GVfsBackend *backend, - GVfsJobOpenForWrite *job, - const char *filename, - const char *etag, - gboolean make_backup, - GFileCreateFlags flags) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - FtpFile *file; - - if (make_backup) - { - /* FIXME: implement! */ - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("backups not supported yet")); - return; - } - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - file = ftp_filename_from_gvfs_path (conn, filename); - do_start_write (ftp, conn, flags, "STOR %s", file); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); - g_free (file); - return; -} - -static void -do_close_write (GVfsBackend *backend, - GVfsJobCloseWrite *job, - GVfsBackendHandle handle) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn = handle; - - ftp_connection_push_job (conn, G_VFS_JOB (job)); - - ftp_connection_close_data_connection (conn); - ftp_connection_receive (conn, 0); - - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -do_write (GVfsBackend *backend, - GVfsJobWrite *job, - GVfsBackendHandle handle, - char *buffer, - gsize buffer_size) -{ - FtpConnection *conn = handle; - gsize n_bytes; - - ftp_connection_push_job (conn, G_VFS_JOB (job)); - - soup_socket_write (conn->data, - buffer, - buffer_size, - &n_bytes, - G_VFS_JOB (job)->cancellable, - &conn->error); - - g_vfs_job_write_set_written_size (job, n_bytes); - ftp_connection_pop_job (conn); -} - -static GList * -do_enumerate_directory (FtpConnection *conn) -{ - gsize size, n_bytes, bytes_read; - SoupSocketIOStatus status; - gboolean got_boundary; - char *name; - GList *list = NULL; - - if (ftp_connection_in_error (conn)) - return NULL; - - size = 128; - bytes_read = 0; - name = g_malloc (size); - - do - { - if (bytes_read + 3 >= size) - { - if (size >= 16384) - { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG, - _("filename too long")); - break; - } - size += 128; - name = g_realloc (name, size); - } - status = soup_socket_read_until (conn->data, - name + bytes_read, - size - bytes_read - 1, - "\n", - 1, - &n_bytes, - &got_boundary, - conn->job->cancellable, - &conn->error); - - bytes_read += n_bytes; - switch (status) - { - case SOUP_SOCKET_EOF: - case SOUP_SOCKET_OK: - if (n_bytes == 0) - { - status = SOUP_SOCKET_EOF; - break; - } - if (got_boundary) - { - name[bytes_read - 1] = 0; - if (bytes_read >= 2 && name[bytes_read - 2] == '\r') - name[bytes_read - 2] = 0; - DEBUG ("--- %s\n", name); - list = g_list_prepend (list, g_strdup (name)); - bytes_read = 0; - } - break; - case SOUP_SOCKET_ERROR: - goto error2; - case SOUP_SOCKET_WOULD_BLOCK: - default: - g_assert_not_reached (); - break; - } - } - while (status == SOUP_SOCKET_OK); - - if (bytes_read) - { - name[bytes_read] = 0; - DEBUG ("--- %s\n", name); - list = g_list_prepend (list, name); - } - else - g_free (name); - - ftp_connection_close_data_connection (conn); - ftp_connection_receive (conn, 0); - if (ftp_connection_in_error (conn)) - goto error; - - return g_list_reverse (list); - -error2: - ftp_connection_close_data_connection (conn); - ftp_connection_receive (conn, 0); -error: - ftp_connection_close_data_connection (conn); - g_list_foreach (list, (GFunc) g_free, NULL); - g_list_free (list); - return NULL; -} - -/* IMPORTANT: SUCK ALARM! - * locks ftp->directory_cache_lock but only iff it returns !NULL */ -static const GList * -enumerate_directory (GVfsBackendFtp *ftp, - FtpConnection * conn, - const FtpFile * dir, - gboolean use_cache) -{ - GList *files; - - g_static_rw_lock_reader_lock (&ftp->directory_cache_lock); - do { - if (use_cache) - files = g_hash_table_lookup (ftp->directory_cache, dir); - else - { - use_cache = TRUE; - files = NULL; - } - if (files == NULL) - { - g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); - ftp->dir_ops->init_data (conn, dir); - files = do_enumerate_directory (conn); - if (files == NULL) - { - return NULL; - } - g_static_rw_lock_writer_lock (&ftp->directory_cache_lock); - g_hash_table_insert (ftp->directory_cache, g_strdup ((const char *) dir), files); - g_static_rw_lock_writer_unlock (&ftp->directory_cache_lock); - files = NULL; - g_static_rw_lock_reader_lock (&ftp->directory_cache_lock); - } - } while (files == NULL); - - return files; -} - -/* NB: This gets a file info for the given object, no matter if it's a dir - * or a file */ -static GFileInfo * -create_file_info (GVfsBackendFtp *ftp, FtpConnection *conn, const char *filename, char **symlink) -{ - const GList *walk, *files; - char *dirname; - FtpFile *dir, *file; - GFileInfo *info; - gpointer iter; - - if (symlink) - *symlink = NULL; - - if (g_str_equal (filename, "/")) - return ftp->dir_ops->get_root (conn); - - dirname = g_path_get_dirname (filename); - dir = ftp_filename_from_gvfs_path (conn, dirname); - g_free (dirname); - - files = enumerate_directory (ftp, conn, dir, TRUE); - if (files == NULL) - { - g_free (dir); - return NULL; - } - - file = ftp_filename_from_gvfs_path (conn, filename); - iter = ftp->dir_ops->iter_new (conn); - for (walk = files; walk; walk = walk->next) - { - info = ftp->dir_ops->iter_process (iter, - conn, - dir, - file, - walk->data, - symlink); - if (info) - break; - } - ftp->dir_ops->iter_free (iter); - g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); - g_free (dir); - g_free (file); - return info; -} - -static GFileInfo * -resolve_symlink (GVfsBackendFtp *ftp, FtpConnection *conn, GFileInfo *original, const char *filename) -{ - GFileInfo *info = NULL; - char *symlink, *newlink; - guint i; - static const char *copy_attributes[] = { - G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK, - G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, - G_FILE_ATTRIBUTE_STANDARD_NAME, - G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, - G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, - G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, - G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET - }; - - if (ftp_connection_in_error (conn)) - return original; - - /* How many symlinks should we follow? - * <alex> maybe 8? - */ - symlink = g_strdup (filename); - for (i = 0; i < 8 && symlink; i++) - { - info = create_file_info (ftp, - conn, - symlink, - &newlink); - if (!newlink) - break; - - g_free (symlink); - symlink = newlink; - } - g_free (symlink); - - if (ftp_connection_in_error (conn)) - { - g_assert (info == NULL); - g_clear_error (&conn->error); - return original; - } - if (info == NULL) - return original; - - for (i = 0; i < G_N_ELEMENTS (copy_attributes); i++) - { - GFileAttributeType type; - gpointer value; - - if (!g_file_info_get_attribute_data (original, - copy_attributes[i], - &type, - &value, - NULL)) - continue; - - g_file_info_set_attribute (info, - copy_attributes[i], - type, - value); - } - g_object_unref (original); - - return info; -} - -static void -do_query_info (GVfsBackend *backend, - GVfsJobQueryInfo *job, - const char *filename, - GFileQueryInfoFlags query_flags, - GFileInfo *info, - GFileAttributeMatcher *matcher) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - GFileInfo *real; - char *symlink; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - if (query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) - { - real = create_file_info (ftp, - conn, - filename, - NULL); - } - else - { - real = create_file_info (ftp, - conn, - filename, - &symlink); - if (symlink) - { - real = resolve_symlink (ftp, conn, real, symlink); - g_free (symlink); - } - } - - if (real) - { - g_file_info_copy_into (real, info); - g_object_unref (real); - } - else if (!ftp_connection_in_error (conn)) - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_NOT_FOUND, - _("File doesn't exist")); - - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -do_enumerate (GVfsBackend *backend, - GVfsJobEnumerate *job, - const char *dirname, - GFileAttributeMatcher *matcher, - GFileQueryInfoFlags query_flags) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - const GList *walk, *files; - FtpFile *dir; - gpointer iter; - GSList *symlink_targets = NULL; - GSList *symlink_fileinfos = NULL; - GSList *twalk, *fwalk; - GFileInfo *info; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - /* no need to check for IS_DIR, because the enumeration code will return that - * automatically. - */ - - dir = ftp_filename_from_gvfs_path (conn, dirname); - files = enumerate_directory (ftp, conn, dir, FALSE); - if (ftp_connection_pop_job (conn)) - { - ftp_connection_push_job (conn, G_VFS_JOB (job)); - if (files != NULL) - { - iter = ftp->dir_ops->iter_new (conn); - for (walk = files; walk; walk = walk->next) - { - char *symlink = NULL; - info = ftp->dir_ops->iter_process (iter, - conn, - dir, - NULL, - walk->data, - query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS ? NULL : &symlink); - if (symlink) - { - /* This is necessary due to our locking. - * And we must not unlock here because it might invalidate the list we iterate */ - symlink_targets = g_slist_prepend (symlink_targets, symlink); - symlink_fileinfos = g_slist_prepend (symlink_fileinfos, info); - } - else if (info) - { - g_vfs_job_enumerate_add_info (job, info); - g_object_unref (info); - } - } - ftp->dir_ops->iter_free (iter); - g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); - for (twalk = symlink_targets, fwalk = symlink_fileinfos; twalk; - twalk = twalk->next, fwalk = fwalk->next) - { - info = resolve_symlink (ftp, conn, fwalk->data, twalk->data); - g_free (twalk->data); - g_vfs_job_enumerate_add_info (job, info); - g_object_unref (info); - } - g_slist_free (symlink_targets); - g_slist_free (symlink_fileinfos); - } - - g_vfs_job_enumerate_done (job); - conn->job = NULL; - g_clear_error (&conn->error); - } - else - g_assert (files == NULL); - - g_vfs_backend_ftp_push_connection (ftp, conn); - g_free (dir); -} - -static void -do_set_display_name (GVfsBackend *backend, - GVfsJobSetDisplayName *job, - const char *filename, - const char *display_name) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - char *name; - FtpFile *original, *dir, *now; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - original = ftp_filename_from_gvfs_path (conn, filename); - name = g_path_get_dirname (filename); - dir = ftp_filename_from_gvfs_path (conn, name); - g_free (name); - now = ftp_filename_construct (conn, dir, display_name); - if (now == NULL) - { - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_INVALID_FILENAME, - _("Invalid filename")); - } - ftp_connection_send (conn, - RESPONSE_PASS_300 | RESPONSE_FAIL_200, - "RNFR %s", original); - g_free (original); - ftp_connection_send (conn, - 0, - "RNTO %s", now); - - name = ftp_filename_to_gvfs_path (conn, now); - g_free (now); - g_vfs_job_set_display_name_set_new_path (job, name); - g_free (name); - gvfs_backend_ftp_purge_cache_directory (ftp, dir); - g_free (dir); - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -do_delete (GVfsBackend *backend, - GVfsJobDelete *job, - const char *filename) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - FtpFile *file; - guint response; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - /* We try file deletion first. If that fails, we try directory deletion. - * The file-first-then-directory order has been decided by coin-toss. */ - file = ftp_filename_from_gvfs_path (conn, filename); - response = ftp_connection_send (conn, - RESPONSE_PASS_500, - "DELE %s", file); - if (STATUS_GROUP (response) == 5) - { - response = ftp_connection_send (conn, - RESPONSE_PASS_500, - "RMD %s", file); - if (response == 550) - { - const GList *files = enumerate_directory (ftp, conn, file, FALSE); - if (files) - { - g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_NOT_EMPTY, - g_strerror (ENOTEMPTY)); - } - else - ftp_connection_set_error_from_response (conn, response); - } - else if (STATUS_GROUP (response) == 5) - { - ftp_connection_set_error_from_response (conn, response); - } - } - - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); - g_free (file); - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -do_make_directory (GVfsBackend *backend, - GVfsJobMakeDirectory *job, - const char *filename) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - FtpFile *file; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - file = ftp_filename_from_gvfs_path (conn, filename); - ftp_connection_send (conn, - 0, - "MKD %s", file); - /* FIXME: Compare created file with name from server result to be sure - * it's correct and otherwise fail. */ - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); - g_free (file); - - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -do_move (GVfsBackend *backend, - GVfsJobMove *job, - const char *source, - const char *destination, - GFileCopyFlags flags, - GFileProgressCallback progress_callback, - gpointer progress_callback_data) -{ - GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; - FtpFile *srcfile, *destfile; - - /* FIXME: what about G_FILE_COPY_NOFOLLOW_SYMLINKS and G_FILE_COPY_ALL_METADATA? */ - - if (flags & G_FILE_COPY_BACKUP) - { - /* FIXME: implement! */ - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("backups not supported yet")); - return; - } - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - srcfile = ftp_filename_from_gvfs_path (conn, source); - destfile = ftp_filename_from_gvfs_path (conn, destination); - if (ftp_connection_try_cd (conn, destfile)) - { - char *basename = g_path_get_basename (source); - FtpFile *real = ftp_filename_construct (conn, destfile, basename); - - g_free (basename); - if (real == NULL) - g_set_error_literal (&conn->error, - G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, - _("Invalid destination filename")); - else - { - g_free (destfile); - destfile = real; - } - } - - if (!(flags & G_FILE_COPY_OVERWRITE)) - { - char *destfilename = ftp_filename_to_gvfs_path (conn, destfile); - GFileInfo *info = create_file_info (ftp, conn, destfilename, NULL); - - g_free (destfilename); - if (info) - { - g_object_unref (info); - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_EXISTS, - _("Target file already exists")); - goto out; - } - } - - ftp_connection_send (conn, - RESPONSE_PASS_300 | RESPONSE_FAIL_200, - "RNFR %s", srcfile); - ftp_connection_send (conn, - 0, - "RNTO %s", destfile); - - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, srcfile); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, destfile); -out: - g_free (srcfile); - g_free (destfile); - g_vfs_backend_ftp_push_connection (ftp, conn); -} - -static void -g_vfs_backend_ftp_class_init (GVfsBackendFtpClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); - - gobject_class->finalize = g_vfs_backend_ftp_finalize; - - backend_class->mount = do_mount; - backend_class->try_mount = try_mount; - backend_class->unmount = do_unmount; - backend_class->open_for_read = do_open_for_read; - backend_class->close_read = do_close_read; - backend_class->read = do_read; - backend_class->create = do_create; - backend_class->append_to = do_append; - backend_class->replace = do_replace; - backend_class->close_write = do_close_write; - backend_class->write = do_write; - backend_class->query_info = do_query_info; - backend_class->enumerate = do_enumerate; - backend_class->set_display_name = do_set_display_name; - backend_class->delete = do_delete; - backend_class->make_directory = do_make_directory; - backend_class->move = do_move; -} |